diff --git a/Kit.Core.Helpers/Class1.cs b/Kit.Core.Helpers/Class1.cs deleted file mode 100644 index 3f2f7f6..0000000 --- a/Kit.Core.Helpers/Class1.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Kit.Core.Helpers -{ - public class Class1 - { - - } -} diff --git a/Kit.Core.Helpers/Kit.Core.Helpers.sln b/Kit.Core.Helpers/Kit.Core.Helpers.sln deleted file mode 100644 index 96ab0af..0000000 --- a/Kit.Core.Helpers/Kit.Core.Helpers.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.14.36301.6 d17.14 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kit.Core.Helpers", "Kit.Core.Helpers.csproj", "{54CF91D7-8299-4DB9-ABF6-4F141A52523A}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {54CF91D7-8299-4DB9-ABF6-4F141A52523A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {54CF91D7-8299-4DB9-ABF6-4F141A52523A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {54CF91D7-8299-4DB9-ABF6-4F141A52523A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {54CF91D7-8299-4DB9-ABF6-4F141A52523A}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {9D8ECF0E-0537-42C4-8DDC-5934B57A874F} - EndGlobalSection -EndGlobal diff --git a/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.AssemblyInfoInputs.cache b/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.AssemblyInfoInputs.cache deleted file mode 100644 index ef09e43..0000000 --- a/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.AssemblyInfoInputs.cache +++ /dev/null @@ -1 +0,0 @@ -1de42344d47904be68c52d310f760c9a33b8396e0e8e253be53c0fe180725da2 diff --git a/Kit.Core.Helpers/obj/project.nuget.cache b/Kit.Core.Helpers/obj/project.nuget.cache deleted file mode 100644 index b727142..0000000 --- a/Kit.Core.Helpers/obj/project.nuget.cache +++ /dev/null @@ -1,8 +0,0 @@ -{ - "version": 2, - "dgSpecHash": "oGlU1raL4Zc=", - "success": true, - "projectFilePath": "C:\\KIT\\Kit.Core\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj", - "expectedPackageFiles": [], - "logs": [] -} \ No newline at end of file diff --git a/Kit.Core.sln b/Kit.Core.sln index eacdac6..bb4865e 100644 --- a/Kit.Core.sln +++ b/Kit.Core.sln @@ -3,7 +3,23 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.14.36301.6 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kit.Core.Helpers", "Kit.Core.Helpers\Kit.Core.Helpers.csproj", "{AF9E1670-5E30-EEEC-3261-A64E6069FE41}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LibCommon", "LibCommon", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kit.Core.Helpers", "LibCommon\Kit.Core.Helpers\Kit.Core.Helpers.csproj", "{45C84BE0-7434-60C9-C314-2ABBEC2244EC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Minio", "LibExternal\Minio\Minio.csproj", "{2EB985EC-7D25-FA14-84F6-8158236A99CF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LibExternal", "LibExternal", "{46D7C78C-2A15-4F28-8498-58FE53B592F0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Npgsql", "LibExternal\Npgsql\Npgsql.csproj", "{AF14796C-37B9-E123-FD4C-4C212045BE8F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.IO.Hashing", "LibExternal\System.IO.Hashing\System.IO.Hashing.csproj", "{B3FA8F79-9DF2-476D-AA0B-F02A201D5997}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Reactive", "LibExternal\System.Reactive\System.Reactive.csproj", "{837A418C-8CCE-5A21-E61F-EA456EEBECBE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kit.Helpers.OpenXml", "LibCommon\Kit.Helpers.OpenXml\Kit.Helpers.OpenXml.csproj", "{2067E7B0-78BB-95B5-68E2-B38C1D158621}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kit.Sign", "LibCommon\Kit.Sign\Kit.Sign.csproj", "{4F7F1524-5EC0-455A-3046-36E2252BC850}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -11,14 +27,47 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {AF9E1670-5E30-EEEC-3261-A64E6069FE41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AF9E1670-5E30-EEEC-3261-A64E6069FE41}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AF9E1670-5E30-EEEC-3261-A64E6069FE41}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AF9E1670-5E30-EEEC-3261-A64E6069FE41}.Release|Any CPU.Build.0 = Release|Any CPU + {45C84BE0-7434-60C9-C314-2ABBEC2244EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {45C84BE0-7434-60C9-C314-2ABBEC2244EC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {45C84BE0-7434-60C9-C314-2ABBEC2244EC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {45C84BE0-7434-60C9-C314-2ABBEC2244EC}.Release|Any CPU.Build.0 = Release|Any CPU + {2EB985EC-7D25-FA14-84F6-8158236A99CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2EB985EC-7D25-FA14-84F6-8158236A99CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2EB985EC-7D25-FA14-84F6-8158236A99CF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2EB985EC-7D25-FA14-84F6-8158236A99CF}.Release|Any CPU.Build.0 = Release|Any CPU + {AF14796C-37B9-E123-FD4C-4C212045BE8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF14796C-37B9-E123-FD4C-4C212045BE8F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF14796C-37B9-E123-FD4C-4C212045BE8F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF14796C-37B9-E123-FD4C-4C212045BE8F}.Release|Any CPU.Build.0 = Release|Any CPU + {B3FA8F79-9DF2-476D-AA0B-F02A201D5997}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B3FA8F79-9DF2-476D-AA0B-F02A201D5997}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B3FA8F79-9DF2-476D-AA0B-F02A201D5997}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B3FA8F79-9DF2-476D-AA0B-F02A201D5997}.Release|Any CPU.Build.0 = Release|Any CPU + {837A418C-8CCE-5A21-E61F-EA456EEBECBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {837A418C-8CCE-5A21-E61F-EA456EEBECBE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {837A418C-8CCE-5A21-E61F-EA456EEBECBE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {837A418C-8CCE-5A21-E61F-EA456EEBECBE}.Release|Any CPU.Build.0 = Release|Any CPU + {2067E7B0-78BB-95B5-68E2-B38C1D158621}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2067E7B0-78BB-95B5-68E2-B38C1D158621}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2067E7B0-78BB-95B5-68E2-B38C1D158621}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2067E7B0-78BB-95B5-68E2-B38C1D158621}.Release|Any CPU.Build.0 = Release|Any CPU + {4F7F1524-5EC0-455A-3046-36E2252BC850}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4F7F1524-5EC0-455A-3046-36E2252BC850}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4F7F1524-5EC0-455A-3046-36E2252BC850}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4F7F1524-5EC0-455A-3046-36E2252BC850}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {45C84BE0-7434-60C9-C314-2ABBEC2244EC} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {2EB985EC-7D25-FA14-84F6-8158236A99CF} = {46D7C78C-2A15-4F28-8498-58FE53B592F0} + {AF14796C-37B9-E123-FD4C-4C212045BE8F} = {46D7C78C-2A15-4F28-8498-58FE53B592F0} + {B3FA8F79-9DF2-476D-AA0B-F02A201D5997} = {46D7C78C-2A15-4F28-8498-58FE53B592F0} + {837A418C-8CCE-5A21-E61F-EA456EEBECBE} = {46D7C78C-2A15-4F28-8498-58FE53B592F0} + {2067E7B0-78BB-95B5-68E2-B38C1D158621} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {4F7F1524-5EC0-455A-3046-36E2252BC850} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9D8ECF0E-0537-42C4-8DDC-5934B57A874F} EndGlobalSection diff --git a/LibCommon/Kit.Core.Helpers/AssemblyHelper.cs b/LibCommon/Kit.Core.Helpers/AssemblyHelper.cs new file mode 100644 index 0000000..1458f9b --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/AssemblyHelper.cs @@ -0,0 +1,55 @@ +using System.Reflection; + +namespace Kit.Helpers +{ + public class AssemblyHelper + { + public static void CheckSignatures() + { + // Получаем эталонный открытый ключ из текущей сборки + Assembly currentAssembly = Assembly.GetExecutingAssembly(); + var referencePublicKey = currentAssembly.GetName().GetPublicKey(); + + // Проверяем, что текущая сборка подписана + if (referencePublicKey == null || referencePublicKey.Length == 0) + { + throw new ApplicationException("The current build is not signed."); + } + + // Получаем список всех загруженных сборок + var assemblies = AppDomain.CurrentDomain.GetAssemblies().OrderBy(x => x.FullName).ToList(); + + bool anyInvalid = false; + + // Проверяем каждую сборку + foreach (var assembly in assemblies) + { + AssemblyName assemblyName = assembly.GetName(); + var publicKey = assemblyName.GetPublicKey(); + + if (assemblyName.Name.ToLowerStartsWith("RiskProf") == false) + { + continue; + } + + // Если сборка не подписана + if (publicKey == null || publicKey.Length == 0) + { + anyInvalid = true; + Console.WriteLine($"The assembly {assembly.FullName} is not signed."); + } + // Сравниваем открытый ключ с эталонным + else if (referencePublicKey.SequenceEqual(publicKey) == false) + { + anyInvalid = true; + Console.WriteLine($"The assembly {assembly.FullName} is signed with a different key."); + } + } + + if (anyInvalid) + { + throw new ApplicationException("Not all signatures match the signature of the current build."); + } + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Attributes/NeedToGenerateJsConstAttribute.cs b/LibCommon/Kit.Core.Helpers/Attributes/NeedToGenerateJsConstAttribute.cs new file mode 100644 index 0000000..bf34a68 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Attributes/NeedToGenerateJsConstAttribute.cs @@ -0,0 +1,11 @@ +namespace Kit.Helpers +{ + /// Атрибут, указывающий на то, что для данного статического класса / enum нужно сформировать js-объект + /// Описания класса и полей, которые нужно включить в генерацию, нужно указывать с помощью + [AttributeUsage(AttributeTargets.Class|AttributeTargets.Enum, Inherited = false, AllowMultiple = false)] + public sealed class NeedToGenerateJsConstAttribute : Attribute + { + /// Создавать ли константный объект "{имя класса/enum}Titles" с теми же ключами, но значениями из + public bool NeedTitlesObject { get; set; } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Attributes/NeedToGenerateJsDocAttribute.cs b/LibCommon/Kit.Core.Helpers/Attributes/NeedToGenerateJsDocAttribute.cs new file mode 100644 index 0000000..c60e7b9 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Attributes/NeedToGenerateJsDocAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace Kit.Helpers +{ + /// Атрибут, указывающий на то, что для данного класса нужно сформировать JsDoc- + /// Описания класса и полей, которые нужно включить в генерацию, нужно указывать с помощью + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + public sealed class NeedToGenerateJsDocAttribute : Attribute { } +} diff --git a/LibCommon/Kit.Core.Helpers/Auth/ICurrentUser.cs b/LibCommon/Kit.Core.Helpers/Auth/ICurrentUser.cs new file mode 100644 index 0000000..e02ba8b --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Auth/ICurrentUser.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Kit.Helpers.Auth +{ + public interface ICurrentUser + { + string GetUserToken(); + } +} diff --git a/LibCommon/Kit.Core.Helpers/Auth/SecurityKeyAttribute.cs b/LibCommon/Kit.Core.Helpers/Auth/SecurityKeyAttribute.cs new file mode 100644 index 0000000..a2e36ec --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Auth/SecurityKeyAttribute.cs @@ -0,0 +1,54 @@ +namespace Kit.Helpers.Auth +{ + using Kit.Helpers; + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Mvc.Filters; + using Microsoft.AspNetCore.Routing; + using Microsoft.Extensions.Configuration; + + public class SecurityKeyFilter : IAuthorizationFilter + { + private string _authServiceUrl; + private ISecurityKeyService _securityKeyService; + + public SecurityKeyFilter(IConfiguration configuration, ISecurityKeyService securityKeyService) + { + _authServiceUrl = configuration["Auth:ServiceUrl"]; + _securityKeyService = securityKeyService; + } + + + public bool AllowMultiple { get { return false; } } + + + public void OnAuthorization(AuthorizationFilterContext context) + { + + var routeData = context.HttpContext.GetRouteData(); + + //// securityKeyName + string securityKeyName = routeData.DataTokens["APP_SecurityKeyName"] as string; + if (string.IsNullOrWhiteSpace(securityKeyName)) return; + + //// requestSecurityKeyValue + string requestSecurityKeyValue = context.HttpContext.Request.Headers.ReadAuthHeader(); + if (!_securityKeyService.CheckKey(securityKeyName, requestSecurityKeyValue)) + { + + string method = context.HttpContext.Request.Method.ToLower(); + string href = $"{_authServiceUrl}/noaccess/{method}"; + string hrefWithQuery = href + context.HttpContext.Request.QueryString; + context.Result = new RedirectResult(hrefWithQuery); + + if (method.Equals("get")) + { + context.Result = new RedirectResult(hrefWithQuery); + } + if (method.Equals("post")) + { + context.Result = new JsonResult(new { State = 307, Url = hrefWithQuery }); + } + } + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Auth/SecurityKeyService.cs b/LibCommon/Kit.Core.Helpers/Auth/SecurityKeyService.cs new file mode 100644 index 0000000..0cc80fb --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Auth/SecurityKeyService.cs @@ -0,0 +1,50 @@ +namespace Kit.Helpers.Auth +{ + using System.Collections.Generic; + + public interface ISecurityKeyRegisterService + { + ISecurityKeyRegisterService RegisterKey(string name, string value); + } + + public interface ISecurityKeyService + { + bool CheckKey(string name, string value); + string GetKey(string name); + } + + public class SecurityKeyService : ISecurityKeyService, ISecurityKeyRegisterService + { + private readonly IDictionary _securityKeys; + + public SecurityKeyService() + { + _securityKeys = new Dictionary(); + } + + public bool CheckKey(string name, string value) + { + string targetValue; + if(_securityKeys.TryGetValue(name, out targetValue)) + { + if (string.IsNullOrWhiteSpace(targetValue)) return true; + return targetValue == value; + } + return false; + } + + public string GetKey(string name) + { + string targetValue; + return _securityKeys.TryGetValue(name, out targetValue) + ? targetValue + : string.Empty; + } + + public ISecurityKeyRegisterService RegisterKey(string name, string value) + { + _securityKeys[name] = value; + return this; + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Cache/BaseCacheProvider.cs b/LibCommon/Kit.Core.Helpers/Cache/BaseCacheProvider.cs new file mode 100644 index 0000000..3f40d0e --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Cache/BaseCacheProvider.cs @@ -0,0 +1,96 @@ +using Microsoft.Extensions.Caching.Memory; +using Kit.Helpers; + +namespace Kit.Helpers.Cache +{ + public abstract class BaseCacheProvider : ICacheProvider + { + + private readonly Dictionary _lockObjects = new Dictionary(1000); + + private readonly object _keysLock = new object(); + + public abstract bool Contains(string cacheName); + public abstract void PurgeCacheItems(string cacheName, bool isPrefix = false); + public abstract void SetCacheData(string cacheName, TObject data, MemoryCacheEntryOptions? setProperties = null, PostEvictionDelegate? callback = null); + public abstract TResult GetCacheData(string cacheName); + + protected string CacheNameNormalize(string casheName) + { + if (casheName == null) return string.Empty; + + return casheName.ToLower(); + } + protected object GetLockObject(string cacheName) + { + lock (_keysLock) + { + if (!this._lockObjects.ContainsKey(cacheName)) this._lockObjects.Add(cacheName, new object()); + return this._lockObjects[cacheName]; + } + } + + public virtual string CreateCacheName(object prefix, params object[] args) + { + prefix = prefix != null ? prefix.ToString()!.AddSufixIsNotEmpty("_") : string.Empty; + + string end = string.Empty; + if (!args.IsNullOrEmpty()) + { + end = args.Where(x => x != null).Select(x => x.ToString()).Join("_"); + } + + return $"{prefix}{end}"; + } + + public virtual TResult GetCacheData(string cacheName, Func getMethod, MemoryCacheEntryOptions setProperties = null) + { + cacheName = CacheNameNormalize(cacheName); + + Check.ArgumentIsNotNull(getMethod, "getMethod"); + if (cacheName.IsNullOrEmpty()) return (TResult)getMethod(); + + // если есть данные в кеше но возвращаем + TResult data = GetCacheData(cacheName); + if (data != null) return data; + + lock (this.GetLockObject(cacheName)) + { + // повторная проверка после блокировки + data = this.GetCacheData(cacheName); + if (data != null) return data; + + // если в кеше данных нет то выполняем метод + data = getMethod(); + this.SetCacheData(cacheName, data, setProperties); + } + + return data; + } + + public TResult GetCacheData(string cacheName, Func getMethod, MemoryCacheEntryOptions? setProperties = null, PostEvictionDelegate? callback = null) + { + cacheName = CacheNameNormalize(cacheName); + + Check.ArgumentIsNotNull(getMethod, "getMethod"); + if (cacheName.IsNullOrEmpty()) return (TResult)getMethod(); + + // если есть данные в кеше но возвращаем + TResult data = GetCacheData(cacheName); + if (data != null) return data; + + lock (this.GetLockObject(cacheName)) + { + // повторная проверка после блокировки + data = this.GetCacheData(cacheName); + if (data != null) return data; + + // если в кеше данных нет то выполняем метод + data = getMethod(); + this.SetCacheData(cacheName, data, setProperties, callback); + } + + return data; + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Cache/ICacheProvider.cs b/LibCommon/Kit.Core.Helpers/Cache/ICacheProvider.cs new file mode 100644 index 0000000..117ebca --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Cache/ICacheProvider.cs @@ -0,0 +1,20 @@ +namespace Kit.Helpers.Cache +{ + using Microsoft.Extensions.Caching.Memory; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + public interface ICacheProvider + { + string CreateCacheName(object prefix, params object[] args); + bool Contains(string cacheName); + void PurgeCacheItems(string cacheName, bool isPrefix = false); + + void SetCacheData(string cacheName, TObject data, MemoryCacheEntryOptions? setProperties = null, PostEvictionDelegate? callback = null); + TResult GetCacheData(string cacheName); + TResult GetCacheData(string cacheName, Func getMethod, MemoryCacheEntryOptions? setProperties = null, PostEvictionDelegate? callback = null); + } +} diff --git a/LibCommon/Kit.Core.Helpers/Cache/MemoryCacheExtensions.cs b/LibCommon/Kit.Core.Helpers/Cache/MemoryCacheExtensions.cs new file mode 100644 index 0000000..5ef71d9 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Cache/MemoryCacheExtensions.cs @@ -0,0 +1,23 @@ +namespace Kit.Helpers.Cache +{ + using Microsoft.Extensions.Caching.Memory; + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + public static class MemoryCacheExtensions + { + private static readonly Func GetEntriesCollection = Delegate.CreateDelegate( + typeof(Func), + typeof(MemoryCache).GetProperty("EntriesCollection", BindingFlags.NonPublic | BindingFlags.Instance).GetGetMethod(true), + throwOnBindFailure: true) as Func; + + public static IEnumerable GetKeys(this IMemoryCache memoryCache) => + ((IDictionary)GetEntriesCollection((MemoryCache)memoryCache)).Keys; + + public static IEnumerable GetKeys(this IMemoryCache memoryCache) => + GetKeys(memoryCache).OfType(); + } +} diff --git a/LibCommon/Kit.Core.Helpers/Cache/MemoryCacheProvider.cs b/LibCommon/Kit.Core.Helpers/Cache/MemoryCacheProvider.cs new file mode 100644 index 0000000..4d8b74c --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Cache/MemoryCacheProvider.cs @@ -0,0 +1,84 @@ +namespace Kit.Helpers.Cache +{ + using Microsoft.Extensions.Caching.Memory; + using Kit.Helpers; + using System; + using System.Collections; + using System.Collections.Generic; + + public interface IHttpCacheProvider : ICacheProvider { } + + public class MemoryCacheProvider : BaseCacheProvider, IHttpCacheProvider + { + private IMemoryCache _memoryCache; + + public MemoryCacheProvider(IMemoryCache memoryCache) + { + _memoryCache = memoryCache; + } + + + public override bool Contains(string cacheName) + { + cacheName = this.CacheNameNormalize(cacheName); + object obj = null; + return _memoryCache.TryGetValue(cacheName, out obj); + } + + public override TResult GetCacheData(string cacheName) + { + cacheName = CacheNameNormalize(cacheName); + + object? result = this._memoryCache.Get(cacheName); + if (result == null) return default!; + + Check.IsTrue(result is TResult, $"Cache value has type:{result.GetType().FullName}. ArgumentType:{typeof(TResult).FullName}"); + + return (TResult)result; + } + + + public override void PurgeCacheItems(string cacheName, bool isPrefix = false) + { + cacheName = CacheNameNormalize(cacheName); + + if (isPrefix) + { + var itemsToRemove = new List(); + + foreach (DictionaryEntry item in this._memoryCache.GetKeys()) + { + string key = item.Key.ToString()!; + if (key.StartsWith(cacheName)) + { + itemsToRemove.Add(key); + } + } + //удаляем элементы кэша + itemsToRemove.ForEach(x => this._memoryCache.Remove(x)); + } + else + { + this._memoryCache.Remove(cacheName); + } + } + + public override void SetCacheData(string cacheName, TObject data, MemoryCacheEntryOptions? setProperties = null, PostEvictionDelegate? callback = null) + { + if (data == null) return; + cacheName = CacheNameNormalize(cacheName); + + if (setProperties == null) + { + setProperties = new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1) }; + setProperties.Priority = CacheItemPriority.Normal; + if (callback != null) + { + setProperties.RegisterPostEvictionCallback(callback); + } + } + + this._memoryCache.Set(cacheName, data, setProperties); + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Check.cs b/LibCommon/Kit.Core.Helpers/Check.cs new file mode 100644 index 0000000..94bcbb2 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Check.cs @@ -0,0 +1,257 @@ +namespace Kit.Helpers +{ + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + + /// + /// a static class that checks if parameter is invalid and raises an exception in this case + /// + public static class Check + { + [DebuggerStepThrough] + public static void ArgumentIsNotNull([AllowNull][NotNull] object argument, string name) + { + if (argument == null) + { + throw new ArgumentNullException(name); + } + } + + [DebuggerStepThrough] + public static void ArgumentIsNotNull([AllowNull][NotNull] object argument, string name, string message) + { + if (argument == null) + { + throw new ArgumentException(message, name); + } + } + + [DebuggerStepThrough] + public static void ArgumentIsNotNullOrEmpty([AllowNull][NotNull] string argument, string name) + { + if (string.IsNullOrWhiteSpace(argument)) + { + throw new ArgumentNullException(name); + } + } + + [DebuggerStepThrough] + public static void ArgumentIsNotNullOrEmpty([AllowNull][NotNull] string argument, string name, string message) + { + if (string.IsNullOrWhiteSpace(argument)) + { + throw new ArgumentException(message, name); + } + } + + [DebuggerStepThrough] + public static void IntegerArgumentPositive(Int32 value, string name) + { + if (value < 1) throw new ArgumentOutOfRangeException(name, value, string.Empty); + } + + [DebuggerStepThrough] + public static void IntegerArgumentPositive(Int32? value, string name) + { + if (!value.HasValue) throw new ArgumentNullException(name); + if (value.Value < 1) throw new ArgumentOutOfRangeException(name, value.Value, string.Empty); + } + + [DebuggerStepThrough] + public static void IntegerPositive(Int16 value, string message) + { + if (value < 1) throw new InvalidOperationException(message); + } + + [DebuggerStepThrough] + public static void IntegerPositive(Int32 value, string message) + { + if (value < 1) throw new InvalidOperationException(message); + } + + [DebuggerStepThrough] + public static void IntegerPositive(Int64 value, string message) + { + if (value < 1) throw new InvalidOperationException(message); + } + + [DebuggerStepThrough] + public static void IntegerPositive(Int32? value, string message) + { + if (!value.HasValue) throw new InvalidOperationException(message); + if (value.Value < 1) throw new InvalidOperationException(message); + } + + [DebuggerStepThrough] + public static void IntegerArgumentPositive(Int64 value, string name) + { + if (value < 1) throw new ArgumentOutOfRangeException(name, value, string.Empty); + } + + [DebuggerStepThrough] + public static void IntegerArgumentPositive(Int64? value, string name) + { + if (!value.HasValue) throw new ArgumentNullException(name); + if (value.Value < 1) throw new ArgumentOutOfRangeException(name, value.Value, string.Empty); + } + + [DebuggerStepThrough] + public static void StringArgumentIsNotNullOrEmpty(string str, string? paramName, string? message) + { + if (string.IsNullOrEmpty(str)) + throw new ArgumentNullException(paramName: paramName, message: message); + + str = str.Trim(); + if (string.IsNullOrEmpty(str)) + throw new ArgumentNullException(paramName: paramName, message: message); + } + + [DebuggerStepThrough] + public static void IsModelStateValid(Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary modelState, string? message = null) + { + if (modelState.IsValid) return; + if (string.IsNullOrWhiteSpace(message) == false) + { + throw new InvalidOperationException(message); + } + + var allErrors = modelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList(); + + throw new InvalidOperationException(allErrors.Join(Environment.NewLine)); + } + + [DebuggerStepThrough] + public static void IsTrue(bool argument, string message) + { + if (argument == false) + { + throw new InvalidOperationException(message); + } + } + + [DebuggerStepThrough] + public static void IsNotTrue(bool argument, string message) + { + if (argument == true) + { + throw new InvalidOperationException(message); + } + } + + [DebuggerStepThrough] + public static void IsNull([AllowNull][NotNull] object argument, string message) + { + if (argument != null) + { + throw new InvalidOperationException(message); + } + } + + [DebuggerStepThrough] + public static void IsNotNull([AllowNull][NotNull] object argument, string message) + { + if (argument == null) + { + throw new InvalidOperationException(message); + } + } + + [DebuggerStepThrough] + public static void IsNullOrEmpty(Guid? argument, string message) + { + if (argument.HasValue == false || argument.Value.Equals(Guid.Empty)) + { + return; + } + + throw new InvalidOperationException(message); + } + + [DebuggerStepThrough] + public static void IsEmpty(Guid argument, string message) + { + if (argument.Equals(Guid.Empty)) + { + return; + } + + throw new InvalidOperationException(message); + } + + [DebuggerStepThrough] + public static void IsNotNullOrEmpty([AllowNull][NotNull]Guid? argument, string message) + { + if (argument.HasValue && argument.Value.Equals(Guid.Empty) == false) + { + return; + } + + throw new InvalidOperationException(message); + } + + [DebuggerStepThrough] + public static void IsNotEmpty(Guid argument, string message) + { + if (argument.Equals(Guid.Empty) == false) + { + return; + } + + throw new InvalidOperationException(message); + } + + [DebuggerStepThrough] + public static void IsNullOrEmpty([AllowNull][NotNull] IEnumerable argument, string message) + { + if (argument.IsNullOrEmpty() == false) + { + throw new InvalidOperationException(message); + } + } + + [DebuggerStepThrough] + public static void IsNotNullOrEmpty([AllowNull][NotNull] IEnumerable argument, string message) + { + if (argument.IsNullOrEmpty()) + { + throw new InvalidOperationException(message); + } + } + /// + /// Checks whether argument is of specified type + /// Throws InvalidOperationException if argument is of wrong type + /// + /// the argument to be checked + /// the Type + /// Implied to contain a {0} placeholder within in order to be replaced with string representation of the type param + [DebuggerStepThrough] + public static void ArgumentIsOfType(object? argument, Type type, string message) + { + Check.ArgumentIsNotNull(argument, "argument"); + Check.ArgumentIsNotNull(type, "type"); + + if (type != argument.GetType()) + { + throw new InvalidOperationException(string.Format(message, type.ToString())); + } + } + + [DebuggerStepThrough] + public static void IsNotNullOrWhiteSpace([AllowNull][NotNull] string? value, string message) + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new InvalidOperationException(message); + } + } + [DebuggerStepThrough] + public static void IsNullOrWhiteSpace([AllowNull][NotNull] string? value, string message) + { + if (string.IsNullOrWhiteSpace(value) == false) + { + throw new InvalidOperationException(message); + } + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Config/DependencyInjectionXmlUpdateItems.cs b/LibCommon/Kit.Core.Helpers/Config/DependencyInjectionXmlUpdateItems.cs new file mode 100644 index 0000000..5ecb6ba --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Config/DependencyInjectionXmlUpdateItems.cs @@ -0,0 +1,36 @@ +using Microsoft.Extensions.DependencyInjection; +using Kit.Helpers.Log; +using Kit.Helpers.Repository.Xml; +using Kit.Helpers.Service; + +namespace Kit.Helpers.Config; + +public static class DependencyInjectionXmlUpdateItems +{ + public static IServiceCollection AddModuleXmlVersionService(this IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + services.RegisterUpdateItemsViaReflection(); + + return services; + } + + private static void RegisterUpdateItemsViaReflection(this IServiceCollection services) + { + var interfaceType = typeof(IXmlVersionUpdateItem); + + var callStackLog = new CallStackLog(); + callStackLog.BeginCall("Get Assemblies"); + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + callStackLog.EndCall(); + + callStackLog.BeginCall("Get All Implementations"); + IEnumerable implementationTypes = assemblies.SelectMany(x => x.GetTypes()).Where(t => interfaceType.IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract); + callStackLog.EndCall(); + + var message = callStackLog.LogMessage; + + implementationTypes.ForEach(implementationType => services.AddTransient(interfaceType, implementationType)); + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Config/StaticFileConfigBuilder.cs b/LibCommon/Kit.Core.Helpers/Config/StaticFileConfigBuilder.cs new file mode 100644 index 0000000..403cb9f --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Config/StaticFileConfigBuilder.cs @@ -0,0 +1,81 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; + +namespace Kit.Helpers.Config; + +public class StaticFileConfigBuilder + where TIApplicationBuilder : IApplicationBuilder +{ + private readonly List _contentModules; + + private Action _onPrepareResponse; + + internal StaticFileConfigBuilder() + { + _contentModules = new List(); + _onPrepareResponse = ctx => + { + ctx.Context.Response.Headers.Append("Cache-Control", "public,max-age=3600"); + }; + } + + public StaticFileConfigBuilder WithContentModule(string moduleName) + { + _contentModules.Add(moduleName); + return this; + } + + public StaticFileConfigBuilder WithOnPrepareResponse(Action ctx) + { + _onPrepareResponse = ctx; + return this; + } + + internal TIApplicationBuilder Apply(TIApplicationBuilder app) + { + IWebHostEnvironment env = app.ApplicationServices.GetRequiredService(); + // основной wwwroot + List fileProviderItems = new List { env.WebRootFileProvider }; + + if (_contentModules.Count > 0) + { +#if DEBUG + fileProviderItems.Add(new PhysicalFileProvider(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot"))); +#else + fileProviders.AddRange(_contentModules.Select(moduleName => new PhysicalFileProvider(Path.Combine(_env.ContentRootPath, "wwwroot", "_content", moduleName)))); +#endif + } + + var fileProvider = new CompositeFileProvider(fileProviderItems); + env.WebRootFileProvider = fileProvider; + app.UseStaticFiles(new StaticFileOptions + { + FileProvider = fileProvider, + RequestPath = "", + OnPrepareResponse = _onPrepareResponse + }); + + return app; + } +} + +public static class StaticFileConfigBuilderExt +{ + + public static TIApplicationBuilder UseCustomStaticFiles(this TIApplicationBuilder app, Action> options) + where TIApplicationBuilder : IApplicationBuilder + { + var config = new StaticFileConfigBuilder(); + + if (options != null) + options(config); + + config.Apply(app); + + return app; + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/ConfigurationHelper.cs b/LibCommon/Kit.Core.Helpers/ConfigurationHelper.cs new file mode 100644 index 0000000..88e1b10 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/ConfigurationHelper.cs @@ -0,0 +1,95 @@ +using Microsoft.Extensions.Configuration; + +namespace Kit.Helpers +{ + public interface IConfigurationItem + { + DateTime FillDate { get; set; } + + void Init(IConfiguration configuration); + } + + public class ConfigurationHelper + { + private readonly string _prefix; + private readonly IConfiguration _configuration; + + public ConfigurationHelper(IConfiguration configuration) : this(configuration, string.Empty) + { + _configuration = configuration; + } + + public ConfigurationHelper(IConfiguration configuration, string prefix) + { + _configuration = configuration; + _prefix = prefix; + } + + private string CreateKey(string itemKey) + { + return string.IsNullOrEmpty(_prefix) + ? itemKey.ToString() + : $"{_prefix}:{itemKey}"; + } + + public string GetConnectionString(string name, bool required = true) + { + string connectionStringName = this.CreateKey(name); + + string connectionString = _configuration.GetConnectionString(connectionStringName); + + if (required) + { + Check.StringArgumentIsNotNullOrEmpty(connectionString, null, $"connection string not set in config. name={connectionStringName}"); + } + + return connectionString; + } + + public string GetAppSettingValue(object settingsItemKey, bool required = true, string? defaultValue = null) + { + // create key + string appSettingsKey = this.CreateKey(settingsItemKey?.ToString() ?? string.Empty); + + // read + string? configurationValue = _configuration[appSettingsKey] ?? defaultValue; + + // validate + if (required && string.IsNullOrWhiteSpace(configurationValue)) + { + throw new Exception($"{appSettingsKey} not set in appSettings section"); + } + + return configurationValue ?? string.Empty; + } + /// + /// + /// + /// + /// + /// + /// The . + public IConfigurationSection GetSection(string name, bool required = true) + { + // create key + string appSettingsKey = this.CreateKey(name.ToString()); + + if (required) + { + return _configuration.GetRequiredSection(appSettingsKey); + } + else + { + return _configuration.GetSection(appSettingsKey); + } + } + + public TEntity? Get(string name) + { + // create key + string key = this.CreateKey(name.ToString()); + + return _configuration.GetSection(key).Get(); + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/ControllerExtensions.cs b/LibCommon/Kit.Core.Helpers/ControllerExtensions.cs new file mode 100644 index 0000000..5bd4169 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/ControllerExtensions.cs @@ -0,0 +1,106 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; +using System.Reflection; + +public static class ControllerExtensions +{ + public static async Task InvokeViewComponentWithModelBindingAsync( + this Controller controller, + Type viewComponentType) + { + + var viewComponent = GetViewComponentInstance(controller, viewComponentType); + var invokeMethod = GetInvokeMethod(viewComponentType); + var parameters = invokeMethod.GetParameters(); + + var parameterValues = await BindParametersAsync(controller, parameters); + + return await ExecuteViewComponentAsync(viewComponent, invokeMethod, parameterValues); + } + + private static ViewComponent GetViewComponentInstance(Controller controller, Type viewComponentType) + { + var instance = controller.HttpContext.RequestServices.GetService(viewComponentType) as ViewComponent; + if (instance == null) + throw new InvalidOperationException($"ViewComponent {viewComponentType.Name} not found."); + return instance; + } + + private static MethodInfo GetInvokeMethod(Type viewComponentType) + { + var method = viewComponentType.GetMethod("InvokeAsync") ?? viewComponentType.GetMethod("Invoke"); + if (method == null) + throw new InvalidOperationException("ViewComponent does not have Invoke/InvokeAsync method."); + return method; + } + + private static async Task BindParametersAsync(Controller controller, ParameterInfo[] parameters) + { + var parameterValues = new object[parameters.Length]; + var modelBindingContexts = new DefaultModelBindingContext[parameters.Length]; + + for (int i = 0; i < parameters.Length; i++) + { + var parameter = parameters[i]; + var bindingContext = CreateBindingContextAsync(controller, parameter) + .GetAwaiter() + .GetResult(); + var binder = GetModelBinder(controller, parameter); + + await binder.BindModelAsync(bindingContext); + parameterValues[i] = bindingContext.Result.IsModelSet + ? bindingContext.Result.Model + : parameter.DefaultValue; + } + + return parameterValues; + } + + private static async Task CreateBindingContextAsync(Controller controller, ParameterInfo parameter) + { + var valueProviders = new List(); + var factoryContext = new ValueProviderFactoryContext(controller.ControllerContext); + + foreach (var factory in controller.ControllerContext.ValueProviderFactories) + { + await factory.CreateValueProviderAsync(factoryContext); + } + + return new DefaultModelBindingContext + { + ActionContext = controller.ControllerContext, + ModelState = controller.ModelState, + ModelMetadata = controller.MetadataProvider.GetMetadataForType(parameter.ParameterType), + FieldName = parameter.Name, + ModelName = parameter.Name, + ValueProvider = new CompositeValueProvider(valueProviders) + }; + } + + private static IModelBinder GetModelBinder(Controller controller, ParameterInfo parameter) + { + var metadata = controller.MetadataProvider.GetMetadataForType(parameter.ParameterType); + return controller.ModelBinderFactory.CreateBinder(new ModelBinderFactoryContext + { + Metadata = metadata, + BindingInfo = new BindingInfo { BindingSource = BindingSource.ModelBinding } + }); + } + + private static async Task ExecuteViewComponentAsync( + ViewComponent viewComponent, + MethodInfo method, + object[] parameters) + { + var result = method.Invoke(viewComponent, parameters); + + if (result is Task task) + { + await task; + return (IViewComponentResult)((dynamic)task).Result; + } + + return (IViewComponentResult)result; + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/CookieHelper.cs b/LibCommon/Kit.Core.Helpers/CookieHelper.cs new file mode 100644 index 0000000..f97cb89 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/CookieHelper.cs @@ -0,0 +1,77 @@ +namespace Kit.Helpers +{ + using System; + using Microsoft.AspNetCore.Http; + + public class CookieHelper + { + public static string GetValue(string key) + { + HttpContext context = HttpContextCore.Current; + return GetValue(context, key); + } + + public static string GetValue(HttpContext context, string key) + { + String result = String.Empty; + if (context != null && context.Request != null && context.Request.Cookies.ContainsKey(key)) + { + return context.Request.Cookies[key]; + } + return result; + } + + public static void SetValue(string key, string value, DateTime expires) + { + HttpContext context = HttpContextCore.Current; + SetValue(context, key, value, expires); + } + + public static void SetValue(HttpContext context, string key, string value, DateTime expires) + { + if (context == null) + { + return; + } + + if (context.Request.Cookies.ContainsKey(key)) + { + context.Response.Cookies.Delete(key); + } + + var cookieOptions = new CookieOptions + { + Expires = expires + }; + context.Response.Cookies.Append(key, value, cookieOptions); + } + + public static void SetValue(string key, string value) + { + SetValue(key, value, DateTime.MaxValue); + } + + public static void SetValue(HttpContext context, string key, string value) + { + SetValue(context, key, value, DateTime.MaxValue); + } + + public static void RemoveCookie(string key) + { + HttpContext context = HttpContextCore.Current; + RemoveCookie(context, key); + } + + public static void RemoveCookie(HttpContext context, string key) + { + if (context == null) + { + return; + } + + CookieOptions cookieOptions = new CookieOptions(); + cookieOptions.Expires = DateTime.Now.AddDays(-30); + context.Response.Cookies.Delete(key, cookieOptions); + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Db/SqlHelper.cs b/LibCommon/Kit.Core.Helpers/Db/SqlHelper.cs new file mode 100644 index 0000000..5a245b8 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Db/SqlHelper.cs @@ -0,0 +1,577 @@ +using System.Data; +using System.Text.Json.Serialization; + +namespace Kit.Helpers; + +public class OutputValue +{ + public object Value; +} + +public class CustomType +{ + public string Name { get; set; } + public Type Type { get; set; } + + public CustomType(string name, Type type) + { + Name = name; + Type = type; + } +} + +public class RequestParams +{ + public IConnectionString ConnectionString { get; set; } = new ConnectionString(); + public string CommandText { get; set; } = string.Empty; + public bool IsStoredProcedure { get; set; } = false; + public bool WithStrictSyntax { get; set; } = false; + public IEnumerable>? Parameters { get; set; } + public IEnumerable? CustomTypes { get; set; } +} + +public class RequestParamsSelect : RequestParams +{ + public IEnumerable>> Converters { get; set; } = Enumerable.Empty>>(); +} + +public interface ISqlHelper +{ + void ExecuteNonQuery(RequestParams requestParams); + object ExecuteScalar(RequestParams requestParams); + TResult ExecuteScalar(RequestParams requestParams); + IEnumerable ExecuteSelectMany(RequestParamsSelect requestParams); + IEnumerableWithPage ExecuteSelectManyWithPage(RequestParamsSelect requestParams); + IEnumerableWithOffer ExecuteSelectManyWithOffer(RequestParamsSelect requestParams); +} + +public interface ISqlHelperFluent +{ + ISqlHelperFluent AsStoredProcedure(); + ISqlHelperFluent AsSqlText(); + ISqlHelperFluent WithStrictSyntax(); + ISqlHelperFluent AddCustomType(string name, Type type); + ISqlHelperFluent AddCustomType(string name); + ISqlHelperFluent AddFilter(string name, object value); + ISqlHelperFluent AddFilter(string name, string value); + ISqlHelperFluent AddParameter(string name, object value); + ISqlHelperFluent AddParameter(string name, string value); + ISqlHelperFluent AddParameterNullable(string name, object? value); + ISqlHelperFluent AddParameterIfNotNull(string name, object? value); + ISqlHelperFluent AddOutputParameter(string name, object value); + ISqlHelperFluent AddParameters(IEnumerable> parameters); + ISqlHelperFluent AddFilters(IEnumerable> parameters); + void ExecuteNonQuery(); + TResult ExecuteScalar(); +} + +public interface ISqlHelperFluent +{ + ISqlHelperFluent AsStoredProcedure(); + ISqlHelperFluent AsSqlText(); + ISqlHelperFluent WithStrictSyntax(); + ISqlHelperFluent AddCustomType(string name, Type type); + ISqlHelperFluent AddCustomType(string name); + ISqlHelperFluent AddFilter(string name, object value); + ISqlHelperFluent AddFilter(string name, string value); + ISqlHelperFluent AddParameter(string name, object value); + ISqlHelperFluent AddParameter(string name, string value); + ISqlHelperFluent AddParameterNullable(string name, object? value); + ISqlHelperFluent AddParameterIfNotNull(string name, object? value); + ISqlHelperFluent AddOutputParameter(string name, object value); + ISqlHelperFluent AddParameters(IEnumerable> parameters); + ISqlHelperFluent AddFilters(IEnumerable> parameters); + ISqlHelperFluent AddConverter(Action> converter); + IEnumerable ExecuteSelectMany(); + IEnumerableWithPage ExecuteSelectManyWithPage(); + IEnumerableWithOffer ExecuteSelectManyWithOffer(); +} + +public enum ConnectionStringType +{ + Undefined = 0, + Postgres = 1, + SQLite = 2, +} + +public interface IConnectionString +{ + ConnectionStringType Type { get; } + string Value { get; } + [JsonIgnore] + object? DatabaseConnection { get; } +} +public class ConnectionString : IConnectionString +{ + public ConnectionStringType Type { get; set; } = ConnectionStringType.Undefined; + + private string _value = string.Empty; + public string Value + { + get => _value; + set => _value = value.ServerMapPath(); + } + [JsonIgnore] + public object? DatabaseConnection { get; set; } + + public void CreateAndOpenConnection() + { + switch (Type) + { + case ConnectionStringType.Postgres: + DatabaseConnection = new Npgsql.NpgsqlConnection(Value.CreateSQLiteConnectionString()); + (DatabaseConnection as Npgsql.NpgsqlConnection)!.Open(); + break; + case ConnectionStringType.SQLite: + DatabaseConnection = new Microsoft.Data.Sqlite.SqliteConnection(Value.CreateSQLiteConnectionString()); + (DatabaseConnection as Microsoft.Data.Sqlite.SqliteConnection)!.Open(); + break; + default: + throw new NotImplementedException(); + } + + } + + ~ConnectionString() + { + if (DatabaseConnection is IDisposable disposable) disposable.Dispose(); + DatabaseConnection = null; + } +} + +public class SqlHelperFluent : ISqlHelperFluent +{ + protected readonly IConnectionString _connectionString; + protected readonly string _sqlText; + protected bool _isStoredProcedure; + protected bool _isOutputParameter; + protected bool _withStrictSyntax; + protected List _customTypes; + protected List> _parameters; + + public SqlHelperFluent(IConnectionString connectionString, string sqlText) + { + _connectionString = connectionString; + _sqlText = sqlText; + _isStoredProcedure = true; + _isOutputParameter = false; + _withStrictSyntax = false; + _customTypes = new List(); + _parameters = new List>(); + } + + protected ISqlHelper GetSqlHelper() + { + switch (_connectionString.Type) + { + case ConnectionStringType.Postgres: + return Postgres.SqlHelper.Instance; + case ConnectionStringType.SQLite: + return SQLite.SqlHelper.Instance; + default: + throw new NotImplementedException(); + } + } + + + #region fluent methods + + public ISqlHelperFluent AsStoredProcedure() + { + _isStoredProcedure = true; + return this; + } + + public ISqlHelperFluent AsSqlText() + { + _isStoredProcedure = false; + return this; + } + + public ISqlHelperFluent WithStrictSyntax() + { + _withStrictSyntax = true; + return this; + } + + public ISqlHelperFluent AddCustomType(string name, Type type) + { + if (type != null) + { + _customTypes.Add(new CustomType(name, type)); + } + return this; + } + + public ISqlHelperFluent AddCustomType(string name) + { + _customTypes.Add(new CustomType(name, typeof(TType))); + return this; + } + + public ISqlHelperFluent AddFilter(string name, object value) + { + if (value != null) + { + _parameters.Add(new KeyValuePair(name, value)); + } + return this; + } + + public ISqlHelperFluent AddFilter(string name, string value) + { + if (!value.IsNullOrEmpty()) + { + _parameters.Add(new KeyValuePair(name, value)); + } + return this; + } + + public ISqlHelperFluent AddParameter(string name, object value) + { + _parameters.Add(new KeyValuePair(name, value)); + return this; + } + + public ISqlHelperFluent AddParameter(string name, string value) + { + + _parameters.Add(new KeyValuePair(name, value ?? string.Empty)); + return this; + } + + public ISqlHelperFluent AddParameterNullable(string name, object? value) + { + _parameters.Add(new KeyValuePair(name, value ?? DBNull.Value)); + return this; + } + + public ISqlHelperFluent AddParameterIfNotNull(string name, object? value) + { + if (value != null) + { + _parameters.Add(new KeyValuePair(name, value)); + } + return this; + } + + public ISqlHelperFluent AddOutputParameter(string name, object value) + { + _parameters.Add(new KeyValuePair(name, new OutputValue { Value = value ?? DBNull.Value })); + return this; + } + + public ISqlHelperFluent AddParameters(IEnumerable> parameters) + { + _parameters.AddRange(parameters); + return this; + } + + public ISqlHelperFluent AddFilters(IEnumerable> parameters) + { + _parameters.AddRange(parameters.Where(x => x.Value != null)); + return this; + } + + #endregion + + #region finish methods + + private RequestParams BuildParams() => new RequestParams + { + ConnectionString = _connectionString, + CommandText = _sqlText, + IsStoredProcedure = _isStoredProcedure, + WithStrictSyntax = _withStrictSyntax, + Parameters = _parameters, + CustomTypes = _customTypes, + }; + + public void ExecuteNonQuery() + { + GetSqlHelper().ExecuteNonQuery(BuildParams()); + } + + public TResult ExecuteScalar() + { + return GetSqlHelper().ExecuteScalar(BuildParams()); + } + + #endregion +} + +public class SqlHelperFluent : SqlHelperFluent, ISqlHelperFluent +{ + protected List>> _converters; + + public SqlHelperFluent(IConnectionString connectionString, string sqlText) : base(connectionString, sqlText) + { + _converters = new List>>(); + } + #region base fluent methods + + public new ISqlHelperFluent AddCustomType(string name, Type type) + { + base.AddCustomType(name, type); + return this; + } + + public new ISqlHelperFluent AddCustomType(string name) + { + base.AddCustomType(name); + return this; + } + + public new ISqlHelperFluent AddFilter(string name, object value) + { + base.AddFilter(name, value); + return this; + } + + public new ISqlHelperFluent AddFilter(string name, string value) + { + base.AddFilter(name, value); + return this; + } + + public new ISqlHelperFluent AddParameter(string name, object value) + { + base.AddParameter(name, value); + return this; + } + + public new ISqlHelperFluent AddParameter(string name, string value) + { + base.AddParameter(name, value); + return this; + } + + public new ISqlHelperFluent AddParameterNullable(string name, object? value) + { + base.AddParameterNullable(name, value); + return this; + } + + public new ISqlHelperFluent AddParameterIfNotNull(string name, object? value) + { + base.AddParameterIfNotNull(name, value); + return this; + } + + public new ISqlHelperFluent AddOutputParameter(string name, object value) + { + base.AddParameter(name, value); + return this; + } + + public new ISqlHelperFluent AddParameters(IEnumerable> parameters) + { + base.AddParameters(parameters); + return this; + } + + public new ISqlHelperFluent AddFilters(IEnumerable> parameters) + { + base.AddFilters(parameters); + return this; + } + + public new ISqlHelperFluent AsStoredProcedure() + { + base.AsStoredProcedure(); + return this; + } + + public new ISqlHelperFluent AsSqlText() + { + base.AsSqlText(); + return this; + } + + public new ISqlHelperFluent WithStrictSyntax() + { + base.WithStrictSyntax(); + return this; + } + + #endregion + + private RequestParamsSelect BuildParams() => new RequestParamsSelect + { + ConnectionString = _connectionString, + CommandText = _sqlText, + IsStoredProcedure = _isStoredProcedure, + WithStrictSyntax = _withStrictSyntax, + Parameters = _parameters, + CustomTypes = _customTypes, + Converters = _converters, + }; + + public ISqlHelperFluent AddConverter(Action> converter) + { + _converters.Add(converter); + return this; + } + + public IEnumerable ExecuteSelectMany() + { + return GetSqlHelper().ExecuteSelectMany(BuildParams()); + } + + public IEnumerableWithPage ExecuteSelectManyWithPage() + { + return GetSqlHelper().ExecuteSelectManyWithPage(BuildParams()); + } + + public IEnumerableWithOffer ExecuteSelectManyWithOffer() + { + return GetSqlHelper().ExecuteSelectManyWithOffer(BuildParams()); + } +} + +public static class SqLiteHelperFluentExtentions +{ + public static ISqlHelperFluent PrepareExecute(this IConnectionString connectionString, string sqlText) + { + return new SqlHelperFluent(connectionString, sqlText); + } + + public static ISqlHelperFluent PrepareExecute(this IConnectionString connectionString, string sqlText) + { + return new SqlHelperFluent(connectionString, sqlText); + } +} + +public interface IDataRecordSpecified +{ + IDataRecord Src { get; } + Npgsql.INpgsqlNameTranslator NameTranslator { get; } + TValue Convert(object value); +} + +internal class DataRecordSpecified : IDataRecordSpecified +{ + public IDataRecord Src { get; } + public Npgsql.INpgsqlNameTranslator NameTranslator { get; } + public virtual TValue Convert(object value) + { + return (TValue)value; + } + + public DataRecordSpecified(IDataReader dataReader, Npgsql.INpgsqlNameTranslator nameTranslator) + { + Src = dataReader; + NameTranslator = nameTranslator; + } +} + +public static class SqlHelperExtensions +{ + /// + /// Проверка на наличие столбца в запросе + /// + /// + /// + /// + public static bool HasColumn(this IDataRecordSpecified reader, string name) + { + string nameTranslated = reader.NameTranslator.TranslateMemberName(name).ToLower(); + for (int n = 0; n < reader.Src.FieldCount; n++) + { + string fieldNameTranslated = reader.NameTranslator.TranslateMemberName(reader.Src.GetName(n)).ToLower(); + if (fieldNameTranslated == nameTranslated) return true; + } + return false; + } + + private static string GetTranslatedName(this IDataRecordSpecified record, string fieldName) => record.NameTranslator.TranslateMemberName(fieldName); + + private static TValue? TryGet(this IDataRecordSpecified record, string fieldName, bool isStrict) + { + fieldName = GetTranslatedName(record, fieldName); + object value = record.Src[fieldName]; + + if (value == DBNull.Value) + { + if (isStrict) + throw new InvalidCastException(string.Format("{0} {1} нельзя привести к {2}", fieldName, typeof(DBNull).FullName, typeof(TValue).FullName)); + + return default; + } + + try + { + return record.Convert(value); + } + catch (InvalidCastException ex) + { + throw new InvalidCastException(string.Format("{0} {1} нельзя привести к {2}", fieldName, value.GetType().FullName, typeof(TValue).FullName), ex); + } + } + + public static TValue Get(this IDataRecordSpecified record, string fieldName) => record.TryGet(fieldName, isStrict: true)!; + public static TValue GetN(this IDataRecordSpecified record, string fieldName) => record.TryGet(fieldName, isStrict: false)!; + + internal static IList CreateList(this SelectType extraSelectType) + { + switch (extraSelectType) + { + case SelectType.Page: + return new ListWithPage(); + case SelectType.Offer: + return new ListWithOffer(); + case SelectType.Default: + default: + return new List(); + } + } + + internal static void ProcessExtraSelect(this IDataReader reader, Npgsql.INpgsqlNameTranslator translator, IList selectResult, SelectType extraSelectType = SelectType.Default) + { + IDataRecordSpecified readerSpecified = new DataRecordSpecified(reader, translator); + switch (extraSelectType) + { + case SelectType.Page: + { + var result = (IEnumerableWithPage)selectResult; + // считывание данных постраничного вывода + while (reader.Read()) + { + if (readerSpecified.HasColumn("FilteredRows")) result.FilteredRows = readerSpecified.Get("filteredRows"); + if (readerSpecified.HasColumn("TotalRows")) result.TotalRows = readerSpecified.Get("totalRows"); + if (readerSpecified.HasColumn("PageNo")) result.PageNo = readerSpecified.Get("pageNo"); + if (readerSpecified.HasColumn("PageSize")) result.PageSize = readerSpecified.Get("pageSize"); + if (readerSpecified.HasColumn("Sort")) result.Sort = readerSpecified.Get("sort"); + if (readerSpecified.HasColumn("Dir")) result.Dir = readerSpecified.Get("dir"); + } + } + break; + case SelectType.Offer: + { + var result = (IEnumerableWithOffer)selectResult; + // считывание данных постраничного вывода + while (reader.Read()) + { + if (readerSpecified.HasColumn("FilteredRows")) result.FilteredRows = readerSpecified.Get("FilteredRows"); + if (readerSpecified.HasColumn("TotalRows")) result.TotalRows = readerSpecified.Get("TotalRows"); + if (readerSpecified.HasColumn("Start")) result.Start = readerSpecified.Get("Start"); + if (readerSpecified.HasColumn("Length")) result.Lenght = readerSpecified.Get("Length"); + if (readerSpecified.HasColumn("Sort")) result.Sort = readerSpecified.Get("Sort"); + if (readerSpecified.HasColumn("Dir")) result.Dir = readerSpecified.Get("Dir"); + } + } + break; + case SelectType.Default: + default: + break; + } + } + +} + +internal enum SelectType +{ + Default, + Page, + Offer +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Db/SqlHelperPostgres.cs b/LibCommon/Kit.Core.Helpers/Db/SqlHelperPostgres.cs new file mode 100644 index 0000000..d83db0c --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Db/SqlHelperPostgres.cs @@ -0,0 +1,264 @@ +using Npgsql; +using Npgsql.NameTranslation; +using System.Data; +using System.Text; + +namespace Kit.Helpers.Postgres; + +public class SqlHelper : ISqlHelper +{ + private SqlHelper() { } + + public INpgsqlNameTranslator Translator { get; } = new NpgsqlSnakeCaseNameTranslator(); + + public static SqlHelper Instance { get; } = new SqlHelper(); + + private IEnumerable>? UpdateParamNames(IEnumerable>? @params) + { + var result = new List>(); + if (@params.IsNullOrEmpty()) return result; + + @params.ForEach(_param => + { + string key = "_" + Translator.TranslateMemberName(_param.Key.Remove(" ")!.Trim('@', '_')); + result.Add(new KeyValuePair(key, _param.Value)); + }); + return result; + } + + private string GenerateParamsForQuery(IEnumerable>? @params) + { + var sb = new StringBuilder(); + if (@params.IsNullOrEmpty() == false) + { + for (int i = 0; i < @params.Count(); i++) + { + string key = @params.ElementAt(i).Key; + + sb.Append(key).Append(" => :").Append(key); + + if (i < @params.Count() - 1) + { + sb.Append(", "); + } + } + } + return sb.ToString(); + } + + private string TranslateName(string name, bool withStrictSyntax) + { + if (withStrictSyntax) + { + return name; + } + + return name.Remove("[")!.Remove("]")!.Split('.').Select(x => Translator.TranslateMemberName(x)).Join("."); + } + + private NpgsqlCommand AppendTypes(NpgsqlCommand command, RequestParams requestParams) + { + requestParams.CustomTypes?.ForEach(x => + { + command.Connection?.TypeMapper.MapComposite(x.Type, TranslateName(x.Name, requestParams.WithStrictSyntax), Translator); + }); + return command; + } + + private NpgsqlCommand CreateCommand(RequestParams requestParams) + { + string sql = TranslateName(requestParams.CommandText, requestParams.WithStrictSyntax); + + bool hasParameters = requestParams.Parameters != null && requestParams.Parameters.Count() > 0; + + var parameters = UpdateParamNames(requestParams.Parameters); + + + if (requestParams.ConnectionString.Value.IsNullOrEmpty()) throw new ArgumentNullException("connectionString"); + if (sql.IsNullOrEmpty()) throw new ArgumentNullException("sql"); + + var connection = new NpgsqlConnection(requestParams.ConnectionString.Value); + + NpgsqlCommand command = connection.CreateCommand(); + command.CommandTimeout = connection.ConnectionTimeout; + if (requestParams.IsStoredProcedure) + { + sql = $"select * from {sql}({GenerateParamsForQuery(parameters)});"; + } + + command.CommandType = System.Data.CommandType.Text; + command.CommandText = sql; + + if (parameters != null && parameters.Any()) + { + foreach (var keyValuePair in parameters) + { + string key = keyValuePair.Key.Remove("@").Remove(" ").ToLower(); + if (keyValuePair.Value is Guid) + { + command.Parameters.AddWithValue(key.ToLower(), keyValuePair.Value ?? Guid.Empty); + } + else + { + command.Parameters.AddWithValue(key.ToLower(), keyValuePair.Value ?? DBNull.Value); + } + } + } + + return command; + } + + public void ExecuteNonQuery(RequestParams requestParams) + { + NpgsqlCommand command = CreateCommand(requestParams); + using (command.Connection) + { + command.Connection.Open(); + AppendTypes(command, requestParams); + command.ExecuteNonQuery(); + command.Connection.Close(); + } + } + + public object ExecuteScalar(RequestParams requestParams) + { + object returnScalar; + + NpgsqlCommand command = CreateCommand(requestParams); + using (command.Connection) + { + command.Connection.Open(); + AppendTypes(command, requestParams); + returnScalar = command.ExecuteScalar(); + command.Connection.Close(); + } + + return returnScalar; + } + + public TResult ExecuteScalar(RequestParams requestParams) + { + object returnScalar; + + NpgsqlCommand command = CreateCommand(requestParams); + using (command.Connection) + { + command.Connection.Open(); + AppendTypes(command, requestParams); + returnScalar = command.ExecuteScalar(); + command.Connection.Close(); + } + return (TResult)returnScalar; + } + + private IEnumerable ExecuteSelectMany(SelectType extraSelectType, RequestParamsSelect requestParams) + { + if (requestParams.Converters == null || requestParams.Converters.Count() < 1) throw new ArgumentNullException("converters"); + + NpgsqlCommand command = CreateCommand(requestParams); + command.CommandTimeout = command.Connection.ConnectionTimeout; + + IList selectResult = extraSelectType.CreateList(); + + using (command.Connection) + { + command.Connection.Open(); + AppendTypes(command, requestParams); + using (NpgsqlTransaction tran = command.Connection.BeginTransaction()) + { + using (IDataReader readerFirst = command.ExecuteReader()) + { + var dataTypeName = readerFirst.GetDataTypeName(0); // Для первого столбца + if (dataTypeName == "refcursor") + { + ProcessForCursors(readerFirst, command, requestParams.Converters, selectResult, extraSelectType); + } + else + { + ProcessForSimple(readerFirst, requestParams.Converters, selectResult, extraSelectType); + } + + } + tran.Commit(); + } + } + return selectResult; + } + + private void ProcessForSimple(IDataReader reader, IEnumerable>> converters, IList selectResult, SelectType extraSelectType = SelectType.Default) + { + IDataRecordSpecified recordSpecified = new DataRecordSpecified(reader, Translator); + foreach (var converter in converters) + { + while (reader.Read()) + { + converter(recordSpecified, selectResult); + } + reader.NextResult(); + } + reader.ProcessExtraSelect(Translator, selectResult, extraSelectType); + } + + private void CheckCursorCount(int cursorCount, int converterCount, SelectType extraSelectType) + { + switch (extraSelectType) + { + case SelectType.Page: + case SelectType.Offer: + Check.IsTrue(cursorCount >= converterCount + 1, "Количество конвертеров больше количества возвращаемых курсоров"); + break; + case SelectType.Default: + default: + Check.IsTrue(cursorCount >= converterCount, "Количество конвертеров больше количества возвращаемых курсоров"); + break; + } + } + + private void ProcessForCursors(IDataReader readerFirst, NpgsqlCommand command, IEnumerable>> converters, IList selectResult, SelectType extraSelectType = SelectType.Default) + { + var cursors = new List(); + while (readerFirst.Read()) + { + cursors.Add(readerFirst[0].ToString()!); + } + readerFirst.Close(); + + CheckCursorCount(cursors.Count, converters.Count(), extraSelectType); + + for (int i = 0; i < converters.Count(); i++) + { + command.CommandText = $"fetch all in \"{cursors[i]}\""; + command.CommandType = CommandType.Text; + + using (NpgsqlDataReader reader = command.ExecuteReader()) + { + IDataRecordSpecified recordSpecified = new DataRecordSpecified(reader, Translator); + while (reader.Read()) + { + converters.ElementAt(i)(recordSpecified, selectResult); + } + } + } + if (extraSelectType != SelectType.Default) + { + // считывание данных постраничного вывода + command.CommandText = $"fetch all in \"{cursors.Last()}\""; + command.CommandType = CommandType.Text; + + using (NpgsqlDataReader reader = command.ExecuteReader()) + { + reader.ProcessExtraSelect(Translator, selectResult, extraSelectType); + } + } + } + + public IEnumerable ExecuteSelectMany(RequestParamsSelect requestParams) => ExecuteSelectMany(SelectType.Default, requestParams); + public IEnumerableWithPage ExecuteSelectManyWithPage(RequestParamsSelect requestParams) => (ListWithPage)ExecuteSelectMany(SelectType.Page, requestParams); + public IEnumerableWithOffer ExecuteSelectManyWithOffer(RequestParamsSelect requestParams) => (ListWithOffer)ExecuteSelectMany(SelectType.Offer, requestParams); +} + +public static class SqlHelperExtensions +{ + public static TValue Get(this IDataRecordSpecified record, string fieldName) => record.Get(fieldName); + public static TValue GetN(this IDataRecordSpecified record, string fieldName) => record.GetN(fieldName); +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Db/SqlHelperSQLite.cs b/LibCommon/Kit.Core.Helpers/Db/SqlHelperSQLite.cs new file mode 100644 index 0000000..3d80b17 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Db/SqlHelperSQLite.cs @@ -0,0 +1,408 @@ +using Microsoft.Data.Sqlite; +using Npgsql.NameTranslation; +using System.Data; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Text.RegularExpressions; + +namespace Kit.Helpers.SQLite; + +internal class SQLiteDataRecordSpecified : DataRecordSpecified +{ + public SQLiteDataRecordSpecified(IDataReader dataReader, Npgsql.INpgsqlNameTranslator nameTranslator) : base(dataReader, nameTranslator) { } + + public override TValue Convert(object value) => SQliteConvert.Convert(value); +} + +internal static class SQliteConvert +{ + private static readonly IEnumerable _typeConverters = new List + { + new ConverterItem(typeof(bool), typeof(long), (value) => System.Convert.ToBoolean(value)), + new ConverterItem(typeof(byte), typeof(long), (value) => System.Convert.ToByte(value)), + new ConverterItem(typeof(char), typeof(string), (value) => System.Convert.ToChar(value.ToString()!)), + new ConverterItem(typeof(DateOnly), typeof(string), (value) => DateOnly.ParseExact(value.ToString()!, "yyyy-MM-dd")), + new ConverterItem(typeof(DateTime), typeof(string), (value) => DateTime.ParseExact(value.ToString()!, "yyyy-MM-dd HH:mm:ss.FFFFFFF", CultureInfo.InvariantCulture)), + new ConverterItem(typeof(DateTimeOffset), typeof(string), (value) => DateTimeOffset.ParseExact(value.ToString()!, "yyyy-MM-dd HH:mm:ss.FFFFFFFzzz", CultureInfo.InvariantCulture)), + new ConverterItem(typeof(decimal), typeof(string), (value) => System.Convert.ToDecimal(value.ToString()!)), + new ConverterItem(typeof(double), typeof(double), (value) => value), + new ConverterItem(typeof(Guid), typeof(string), (value) => Guid.Parse(value.ToString()!)), + new ConverterItem(typeof(short), typeof(long), (value) => System.Convert.ToInt16(value)), + new ConverterItem(typeof(int), typeof(long), (value) => System.Convert.ToInt32(value)), + new ConverterItem(typeof(long), typeof(long), (value) => value), + new ConverterItem(typeof(sbyte), typeof(long), (value) => System.Convert.ToSByte(value)), + new ConverterItem(typeof(float), typeof(double), (value) => System.Convert.ToSingle(value)), + new ConverterItem(typeof(string), typeof(string), (value) => value.ToString()!), + new ConverterItem(typeof(TimeOnly), typeof(string), (value) => TimeOnly.ParseExact(value.ToString()!, "HH:mm:ss.fffffff", CultureInfo.InvariantCulture)), + new ConverterItem(typeof(TimeSpan), typeof(string), (value) => TimeSpan.ParseExact(value.ToString()!, "d.hh:mm:ss.fffffff", CultureInfo.InvariantCulture)), + new ConverterItem(typeof(ushort), typeof(long), (value) => System.Convert.ToUInt16(value)), + new ConverterItem(typeof(uint), typeof(long), (value) => System.Convert.ToUInt32(value)), + new ConverterItem(typeof(ulong), typeof(long), (value) => System.Convert.ToUInt64(value)), + }; + + private class ConverterItem + { + public Type Tgt { get; set; } + public Type Src { get; set; } + public Func Func { get; set; } + + public ConverterItem(Type tgt, Type src, Func func) + { + this.Tgt = tgt; + this.Src = src; + this.Func = func; + } + } + + public static TValue Convert(object value) + { + Type valueType = value.GetType(); + Type targetType = typeof(TValue); + + return (TValue)(_typeConverters.FirstOrDefault(x => x.Tgt == targetType && x.Src == valueType)?.Func(value) ?? value); + } +} + +public class SqlHelper : ISqlHelper +{ + private readonly ILockService _lockService = new LockService(); + private SqlHelper() { } + + public Npgsql.INpgsqlNameTranslator Translator { get; } = new NpgsqlSnakeCaseNameTranslator(); + + public static SqlHelper Instance { get; } = new SqlHelper(); + + private List> UpdateParamNames(RequestParams requestParams) + { + var result = new List>(); + if (requestParams.Parameters.IsNullOrEmpty()) return result; + + requestParams.Parameters.ForEach(_param => + { + string key = ":" + (requestParams.WithStrictSyntax ? _param.Key : Translator.TranslateMemberName(_param.Key.Remove(" ")!.Trim('@', '_'))); + result.Add(new KeyValuePair(key, _param.Value)); + }); + return result; + } + + private SqliteConnection CreateConnection(IConnectionString fileName) + { + var conn = new SqliteConnection(fileName.Value.CreateSQLiteConnectionString()); + conn.Open(); + return conn; + } + + private Func _funcStrToIntJsonArr = (string input, string delimiter) => + { + if (string.IsNullOrWhiteSpace(input)) + { + return new List().ToJSON(); + } + if (string.IsNullOrWhiteSpace(delimiter)) + { + delimiter = ","; + } + + HashSet ints = input.Split(delimiter, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).Select(x => int.TryParse(x.Trim(), out int result) ? result : 0).ToHashSet(); + return ints.ToJSON(); + }; + + private class SQLiteProcedureParam + { + public string Name { get; set; } = string.Empty; + public string Type { get; set; } = string.Empty; + public bool IsOptional { get; set; } + [MemberNotNullWhen(true, "DefaultValueParsed")] + public bool HasDefault { get; set; } + public string DefaultValue { get; set; } = string.Empty; + + public SqliteType TypeParsed { get; set; } = SqliteType.Blob; + public object? DefaultValueParsed { get; set; } + } + + private void ValidateAndCompleteParams(string procedureName, string procedureParams, string procedureText, List> parametersUpdated) + { + var errors = new List(); + + IEnumerable textParams = Regex.Matches(procedureText, @"(:\w+)").Select(x => x.Value.ToLower()).Distinct().ToList(); + var paramParsedList = new List(); + + procedureParams.Split(",", StringSplitOptions.TrimEntries).ForEach(x => + { + // Обязательный, без значения по умолчанию + var regexReq = new Regex(@"^(:\w+)\s+(\S+)$"); + + // Необязательный, по умолчанию null + var regexOpt = new Regex(@"^(:\w+)\s+(\S+)(?i:\s*=\s*|\s+default\s+)(?i:null)$"); + // Необязательный, указано значение по умолчанию + var regexOptW = new Regex(@"^(:\w+)\s+(\S+)(?i:\s*=\s*|\s+default\s+)(.*)"); + + Match match = null!; + var param = new SQLiteProcedureParam(); + + if ((match = regexReq.Match(x)).Success) + { + param.Name = match.Groups[1].Value; + param.Type = match.Groups[2].Value; + param.IsOptional = false; + param.HasDefault = false; + param.DefaultValue = string.Empty; + } + else if ((match = regexOpt.Match(x)).Success) + { + param.Name = match.Groups[1].Value; + param.Type = match.Groups[2].Value; + param.IsOptional = true; + param.HasDefault = false; + param.DefaultValue = string.Empty; + } + else if ((match = regexOptW.Match(x)).Success) + { + param.Name = match.Groups[1].Value; + param.Type = match.Groups[2].Value; + param.IsOptional = true; + param.HasDefault = true; + param.DefaultValue = match.Groups[3].Value; + } + else + { + errors.Add($"Invalid parameter: {x}"); + return; + } + + param.Name = param.Name.ToLower(); + + try + { + param.TypeParsed = param.Type.ParseToEnum(ignoreCase: true); + } + catch + { + errors.Add($"parameter '{param.Name}': invalid type ({param.Type})"); + return; + } + + try + { + if (param.HasDefault) + { + switch (param.TypeParsed) + { + case SqliteType.Integer: + param.DefaultValueParsed = int.Parse(param.DefaultValue); + break; + case SqliteType.Real: + param.DefaultValueParsed = double.Parse(param.DefaultValue); + break; + case SqliteType.Text: + if (param.DefaultValue.StartsWith('\'') == false || param.DefaultValue.EndsWith('\'') == false) + throw new Exception(); + + param.DefaultValueParsed = param.DefaultValue.Trim('\'').Replace("''", "'"); + if (((string)param.DefaultValueParsed).IndexOf("''") != -1) + throw new Exception(); + break; + case SqliteType.Blob: + default: + break; + } + } + } + catch + { + errors.Add($"parameter '{param.Name}': invalid default value ({param.DefaultValue})"); + param.DefaultValueParsed = null; + + return; + } + + paramParsedList.Add(param); + }); + + IEnumerable delacrationDuplicates = paramParsedList.GroupBy(x => x.Name.ToLower()).Where(x => x.Count() > 1).Select(x => x.Key).ToList(); + if (delacrationDuplicates.Count() > 0) + { + errors.AddRange(delacrationDuplicates.Select(x => $"There are duplicates of the parameter declaration: {x}")); + } + + IEnumerable notDescribedParams = textParams.Where(x => paramParsedList.Any(y => y.Name == x) == false).ToList(); + if (notDescribedParams.Count() > 0) + { + errors.AddRange(notDescribedParams.Select(x => $"parameter not declared: {x}")); + } + + if (errors.Count > 0) + { + throw new Exception(errors.Join(Environment.NewLine)); + } + + // Ловим неуказанные обязательные параметры + IEnumerable notSendedRequired = paramParsedList.Where(x => x.IsOptional == false && parametersUpdated.Any(y => y.Key == x.Name) == false).Select(x => x.Name).ToList(); + if (notSendedRequired.Count() > 0) + { + errors.AddRange(notSendedRequired.Select(x => $"required parameter is not sended: {x}")); + } + + if (errors.Count > 0) + { + throw new Exception(errors.Join(Environment.NewLine)); + } + + // Ловим неуказанные необязательные параметры + paramParsedList.Where(x => x.IsOptional && parametersUpdated.Any(y => y.Key == x.Name) == false).ForEach(x => + { + if (x.HasDefault) + { + parametersUpdated.Add(new KeyValuePair(x.Name, x.DefaultValueParsed)); + } + else + { + parametersUpdated.Add(new KeyValuePair(x.Name, DBNull.Value)); + } + }); + } + + private void GetStoredProcedureContent(SqliteCommand command, string input, out string procedureName, out string procedureParams, out string procedureText) + { + string oldText = command.CommandText; + CommandType oldType = command.CommandType; + + command.CommandType = CommandType.Text; + command.CommandText = "select procedure_name, params, text from procedures where procedure_name = :procedure_name"; + command.Parameters.AddWithValue(":procedure_name", input); + + using (var reader = command.ExecuteReader()) + { + if (reader.Read() == false) + { + throw new Exception($"procedure '{input}' not found"); + } + + var readerD = new SQLiteDataRecordSpecified(reader, Translator); + + procedureName = readerD.Get("ProcedureName"); + procedureParams = readerD.Get("Params"); + procedureText = readerD.Get("Text"); + + if (procedureName != input) + { + throw new Exception($"invalid procedure name (requested: {input}; received: {procedureName})"); + } + } + command.CommandText = oldText; + command.CommandType = oldType; + command.Parameters.Clear(); + } + + private string GetCommandText(RequestParams requestParams) + { + if (requestParams.WithStrictSyntax) + { + return requestParams.CommandText; + } + + return requestParams.CommandText.Remove("[")!.Remove("]")!.Split('.').Select(x => Translator.TranslateMemberName(x)).Join("_"); + } + + private SqliteCommand CreateCommand(SqliteConnection connection, RequestParams requestParams) + { + if (requestParams.CommandText.IsNullOrEmpty()) throw new ArgumentNullException("sql"); + + string sql = GetCommandText(requestParams); + + var parametersUpdated = UpdateParamNames(requestParams); + + SqliteCommand command = connection.CreateCommand(); + command.CommandTimeout = connection.ConnectionTimeout; + + // Регистрируем пользовательские функции + connection.CreateFunction("str_to_int_json_arr", _funcStrToIntJsonArr, true); + + if (requestParams.IsStoredProcedure) + { + GetStoredProcedureContent(command, sql, out string procedureName, out string procedureParams, out string procedureText); + ValidateAndCompleteParams(procedureName, procedureParams, procedureText, parametersUpdated); + + sql = procedureText; + } + if (sql.IsNullOrEmpty()) throw new ArgumentNullException("sql"); + + command.CommandType = CommandType.Text; + command.CommandText = sql; + + if (parametersUpdated.Count > 0) + { + foreach (var keyValuePair in parametersUpdated) + { + command.Parameters.AddWithValue(keyValuePair.Key, keyValuePair.Value ?? (keyValuePair.Value is Guid ? Guid.Empty : DBNull.Value)); + } + } + + return command; + } + + private TResult ExecuteOnConnect(RequestParams requestParams, Func func) + { + return _lockService.Lock(requestParams.ConnectionString.Value, () => + { + bool needNewConnect = requestParams.ConnectionString.DatabaseConnection == null || requestParams.ConnectionString.DatabaseConnection is SqliteConnection == false; + SqliteConnection connection = needNewConnect ? CreateConnection(requestParams.ConnectionString) : (SqliteConnection)requestParams.ConnectionString.DatabaseConnection!; + + try + { + using (SqliteCommand command = CreateCommand(connection, requestParams)) + return func(command); + } + finally + { + if (needNewConnect) + connection?.Dispose(); + + connection = null!; + } + }); + } + + public void ExecuteNonQuery(RequestParams requestParams) + { + ExecuteOnConnect(requestParams, (SqliteCommand command) => command.ExecuteNonQuery()); + } + + public object ExecuteScalar(RequestParams requestParams) + { + return ExecuteOnConnect(requestParams, (SqliteCommand command) => command.ExecuteScalar()!); + } + + public TResult ExecuteScalar(RequestParams requestParams) + { + return ExecuteOnConnect(requestParams, (SqliteCommand command) => SQliteConvert.Convert(command.ExecuteScalar()!)); + } + + private IEnumerable ExecuteSelectMany(SelectType extraSelectType, RequestParamsSelect requestParams) + { + if (requestParams.Converters == null || requestParams.Converters.Count() < 1) throw new ArgumentNullException("converters"); + + return ExecuteOnConnect(requestParams, (SqliteCommand command) => + { + IList selectResult = extraSelectType.CreateList(); + using (IDataReader reader = command.ExecuteReader()) + { + IDataRecordSpecified readerSpecified = new SQLiteDataRecordSpecified(reader, Translator); + foreach (var converter in requestParams.Converters) + { + while (reader.Read()) + { + converter(readerSpecified, selectResult); + } + reader.NextResult(); + } + reader.ProcessExtraSelect(Translator, selectResult, extraSelectType); + } + return selectResult; + }); + } + + public IEnumerable ExecuteSelectMany(RequestParamsSelect requestParams) => ExecuteSelectMany(SelectType.Default, requestParams); + public IEnumerableWithPage ExecuteSelectManyWithPage(RequestParamsSelect requestParams) => (ListWithPage)ExecuteSelectMany(SelectType.Page, requestParams); + public IEnumerableWithOffer ExecuteSelectManyWithOffer(RequestParamsSelect requestParams) => (ListWithOffer)ExecuteSelectMany(SelectType.Offer, requestParams); +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Dependency/Services.cs b/LibCommon/Kit.Core.Helpers/Dependency/Services.cs new file mode 100644 index 0000000..5ea9978 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Dependency/Services.cs @@ -0,0 +1,69 @@ +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection; +using Kit.Helpers; +using Kit.Helpers.Cache; +using Kit.Helpers.Repository; +using Kit.Helpers.Service; + +namespace RiskProf.Files.Dependency; + +public static class Services +{ + public static IServiceCollection AddModuleMemoryCache(this IServiceCollection services) + { + services.AddSingleton(); + + services.AddSingleton(); + + return services; + } + + public static IServiceCollection AddModuleHardwareId(this IServiceCollection services, bool needDebugProvider) + { + IHardwareInfoProvider infoProvider = null!; + + if (needDebugProvider) + { + infoProvider = new HardwareInfoProviderDebug(); + } + else if (OperatingSystem.IsWindows()) + { + infoProvider = new HardwareInfoProviderWindows(); + } + else if (OperatingSystem.IsLinux()) + { + infoProvider = new HardwareInfoProviderLinux(); + } + + if (infoProvider == null) + { + throw new ApplicationException($"{nameof(infoProvider)} is null"); + } + + services.AddSingleton(infoProvider); + services.AddSingleton(); + + return services; + } + + public static IServiceCollection AddModuleSQLite(this IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSQLiteUpdateItems(); + + return services; + } + + public static void AddSQLiteUpdateItems(this IServiceCollection services) + { + var interfaceType = typeof(ISQLiteVersionUpdateItem); + + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + var implementationTypes = assemblies.SelectMany(x => x.GetTypes()).Where(t => interfaceType.IsAssignableFrom(t) && t.IsInterface == false && t.IsAbstract == false); + + implementationTypes.ForEach(implementationType => services.AddTransient(interfaceType, implementationType)); + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/DictionaryCollector.cs b/LibCommon/Kit.Core.Helpers/DictionaryCollector.cs new file mode 100644 index 0000000..407dc7b --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/DictionaryCollector.cs @@ -0,0 +1,41 @@ +using System.ComponentModel; +using System.Reflection; + +namespace Kit.Helpers +{ + // Класс для сбора словаря + public static class DictionaryCollector + { + public static IDictionary GetConstantsWithDescriptions() + { + return GetConstantsWithDescriptions(typeof(T)); + } + public static IDictionary GetConstantsWithDescriptions(Type type) + { + var result = new Dictionary(); + + // Получаем все поля в переданном статическом классе + var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static); + + foreach (var field in fields) + { + // Проверяем, является ли поле константой + if (field.IsLiteral && !field.IsInitOnly) + { + // Получаем значение константы + var value = field.GetValue(null)?.ToString(); + + // Получаем атрибут Description + var attribute = field.GetCustomAttribute(); + if (attribute != null) + { + // Добавляем пару "значение константы" - "описание" в словарь + result[value] = attribute.Description; + } + } + } + + return result; + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/EndPoints/ControllerEndPointFluent.cs b/LibCommon/Kit.Core.Helpers/EndPoints/ControllerEndPointFluent.cs new file mode 100644 index 0000000..ebb0c2f --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/EndPoints/ControllerEndPointFluent.cs @@ -0,0 +1,46 @@ +using System; + +namespace Kit.Helpers.EndPoints +{ + using Microsoft.AspNetCore.Mvc; + + internal class ControllerEndPointFluent : IEndPointFluent> + where TController : ControllerBase + { + + public ControllerEndPointFluent NeedHttpGet(bool required = true) + { + throw new NotImplementedException(); + } + + public ControllerEndPointFluent NeedHttpPost(bool required = true) + { + throw new NotImplementedException(); + } + + public ControllerEndPointFluent NeedSecurityKey(string securityKeyName) + { + throw new NotImplementedException(); + } + + public ControllerEndPointFluent WithConstraints(object constraints) + { + throw new NotImplementedException(); + } + + public ControllerEndPointFluent WithDataTokens(object dataTokens) + { + throw new NotImplementedException(); + } + + public ControllerEndPointFluent WithDefaults(object defaults) + { + throw new NotImplementedException(); + } + + public ControllerEndPointFluent WithNamespaces(string[] namespaces) + { + throw new NotImplementedException(); + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/EndPoints/IEndPointFluent.cs b/LibCommon/Kit.Core.Helpers/EndPoints/IEndPointFluent.cs new file mode 100644 index 0000000..8060995 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/EndPoints/IEndPointFluent.cs @@ -0,0 +1,13 @@ +namespace Kit.Helpers.EndPoints +{ + public interface IEndPointFluent + { + TEndPointFluent NeedHttpGet(bool required = true); + TEndPointFluent NeedHttpPost(bool required = true); + TEndPointFluent NeedSecurityKey(string securityKeyName); + TEndPointFluent WithDefaults(object defaults); + TEndPointFluent WithConstraints(object constraints); + TEndPointFluent WithNamespaces(string[] namespaces); + TEndPointFluent WithDataTokens(object dataTokens); + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Event/ItemLogEventManager.cs b/LibCommon/Kit.Core.Helpers/Event/ItemLogEventManager.cs new file mode 100644 index 0000000..e389bfd --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Event/ItemLogEventManager.cs @@ -0,0 +1,34 @@ +namespace Kit.Helpers +{ + public class ItemLogEventManager + { + public ItemLogEventManager() { } + + // Пример делегата для события + public delegate void EventHandler(object contentId, IEnumerable itemLog); + + // Событие, на которое могут подписываться другие части приложения + public static event EventHandler OnLog; + + //private static ItemLogEventManager _instance; + + //public static ItemLogEventManager Instance + //{ + // get + // { + // if (_instance == null) + // { + // _instance = new ItemLogEventManager(); + // } + + // return _instance; + // } + //} + + // Метод для вызова события + public static void Invoke(object contentId, IEnumerable itemLogs) + { + OnLog?.Invoke(contentId, itemLogs); + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Exception/AuthenticationException.cs b/LibCommon/Kit.Core.Helpers/Exception/AuthenticationException.cs new file mode 100644 index 0000000..bc12122 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Exception/AuthenticationException.cs @@ -0,0 +1,12 @@ +namespace Kit.Helpers; +using System; + +public class AuthenticationException : Exception +{ + private const string _messageDefault = "Необходимо выполнить вход в систему"; + public string? RedirectUrl { get; set; } + + public AuthenticationException() : base(_messageDefault) { } + public AuthenticationException(string? message) : base(message ?? _messageDefault) { } + public AuthenticationException(string? message, Exception? exception) : base(message ?? _messageDefault, exception) { } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Exception/AuthorizationException.cs b/LibCommon/Kit.Core.Helpers/Exception/AuthorizationException.cs new file mode 100644 index 0000000..c71f687 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Exception/AuthorizationException.cs @@ -0,0 +1,12 @@ +namespace Kit.Helpers; +using System; + +public class AuthorizationException : Exception +{ + private const string _messageDefault = "Нет доступа к выполнению операции"; + public string? RedirectUrl { get; set; } + + public AuthorizationException() : base(_messageDefault) { } + public AuthorizationException(string? message) : base(message ?? _messageDefault) { } + public AuthorizationException(string? message, Exception? exception) : base(message ?? _messageDefault, exception) { } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Exception/ExceptionHelper.cs b/LibCommon/Kit.Core.Helpers/Exception/ExceptionHelper.cs new file mode 100644 index 0000000..937f71c --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Exception/ExceptionHelper.cs @@ -0,0 +1,149 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Security; +using System.Text; + +namespace Kit.Helpers +{ + + public static class ExceptionHelper_Extensions + { + public static string GetInfoAsPlainText(this Exception ex) + { + if (ex == null) return string.Empty; + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("Source: {0}", ex.Source); sb.AppendLine(); + sb.AppendFormat("Message: {0}", ex.Message); sb.AppendLine(); + sb.AppendFormat("StackTrace: {0}", ex.StackTrace); sb.AppendLine(); + if (ex.InnerException != null) + { + sb.AppendLine("InnerException:"); + sb.Append(ex.InnerException.GetInfoAsPlainText()); + } + + return sb.ToString(); + } + + public static string GetInfoAsHtml(this Exception ex) + { + return ex.GetInfoAsPlainText().Replace(Environment.NewLine, "
"); + } + } + + public static class ExceptionExtensions + { + public static string ToString(this Exception exception, ExceptionToStringSettings settings) + { + Exception ex = exception; + + var stringBuilder = new StringBuilder(); + + if (ExceptionToStringSettings.ShowMessageAndInnerExceptionMessages == (ExceptionToStringSettings.ShowMessageAndInnerExceptionMessages & settings)) + { + if (ExceptionToStringSettings.ShowSeparators == (ExceptionToStringSettings.ShowSeparators & settings)) + { + stringBuilder.AppendLine(); + stringBuilder.AppendLine("*********************** Exception Text ************************"); + } + + stringBuilder.Append(ex); + } + + if (ExceptionToStringSettings.ShowAssemblyInformation == (ExceptionToStringSettings.ShowAssemblyInformation & settings)) + { + if (ExceptionToStringSettings.ShowSeparators == (ExceptionToStringSettings.ShowSeparators & settings)) + { + stringBuilder.AppendLine(); + stringBuilder.AppendLine(); + stringBuilder.AppendLine(); + stringBuilder.AppendLine("********************** Loaded Assemblies **********************"); + } + + stringBuilder.Append(GetLoadedAssemblyInfo()); + + } + + return stringBuilder.ToString(); + } + + private static string GetLoadedAssemblyInfo() + { + const string separator = "--------------------------------------------------------------"; + + //new FileIOPermission(PermissionState.Unrestricted).Assert(); + try + { + var stringBuilder = new StringBuilder(); + + Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); + foreach (Assembly assembly in assemblies) + { + AssemblyName name2 = assembly.GetName(); + string text2 = string.Empty; + + try + { + if (!string.IsNullOrEmpty(name2.EscapedCodeBase)) + { + var uri = new Uri(name2.EscapedCodeBase); + if (uri.Scheme == "file") + { + text2 = FileVersionInfo.GetVersionInfo(GetLocalPath(name2.EscapedCodeBase)).FileVersion; + } + } + } + catch (FileNotFoundException) + { + } + + stringBuilder.AppendLine(name2.Name); + stringBuilder.AppendLine(name2.Version.ToString()); + stringBuilder.AppendLine(text2); + stringBuilder.AppendLine(name2.EscapedCodeBase); + stringBuilder.Append(separator); + stringBuilder.AppendLine(); + } + + return stringBuilder.ToString(); + } + finally + { + // TODO: разобраться, что это. В .NET Core 6 признано небезопасным. подключается как + //CodeAccessPermission.RevertAssert(); + } + } + + private static string GetLocalPath(string fileName) + { + var uri = new Uri(fileName); + return uri.LocalPath + uri.Fragment; + } + } + + /// + /// Settings for + /// + [Flags] + public enum ExceptionToStringSettings + { + /// + /// Show message + /// + ShowMessageAndInnerExceptionMessages = 1, + + /// + /// Show assembly information + /// + ShowAssemblyInformation = 2, + + /// + /// Show sepparators + /// + ShowSeparators = 4, + + //All + All = ShowMessageAndInnerExceptionMessages | ShowAssemblyInformation | ShowSeparators + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Exception/ExceptionMessages.cs b/LibCommon/Kit.Core.Helpers/Exception/ExceptionMessages.cs new file mode 100644 index 0000000..58a0c3b --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Exception/ExceptionMessages.cs @@ -0,0 +1,96 @@ +namespace Kit.Helpers +{ + public enum WordGender + { + /// Мужской род + Masculine = 1, + /// Женский род + Feminine = 2, + /// Средний род + Neuter = 3, + } + + public static class ExceptionMessages + { + public const string InvalidContext = "Контекст работника не установлен"; + + public const string InvalidModel = "Передана некорректная модель"; + public const string InvalidModelQuery = "Передана некорректная модель (строка)"; + public const string InvalidModelForm = "Передана некорректная модель (форма)"; + public const string InvalidModelBody = "Передана некорректная модель (тело)"; + public const string InvalidTitle = "Указано некорректное наименование"; + public const string InvalidUserId = "Указан некорректный id пользователя"; + public const string InvalidProjectId = "Указан некорректный id проекта"; + public const string InvalidVersionId = "Указан некорректный id версии"; + public const string InvalidWorkerGuid = "Указан некорректный id работника"; + public const string InvalidElementId = "Указан некорректный id элемента"; + public const string InvalidSecObjPermissions = "Указаны некорректные идентификаторы защищаемых объектов и/или функций"; + public const string InvalidRoles = "Указаны некорректные идентификаторы ролей"; + public const string InvalidWorkers = "Указаны некорректные данные работников"; + public const string InvalidWorkerIds = "Указаны некорректные идентификаторы работников"; + public const string InvalidUserToken = "Указан некорректный токен пользователя"; + public const string InvalidLogin = "Указан некорректный логин"; + public const string InvalidPassword = "Указан некорректный пароль"; + public const string InvalidLoginOrPassword = "Указан некорректный логин или пароль"; + public const string InvalidTempToken = "Указан некорректный временный токен"; + public const string InvalidCheckupItemId = "Указан некорректный id экземпляра проверки"; + public const string InvalidCheckupItemObjectLink = "Указана некорректная ссылка на объект"; + public const string InvalidAttachmentId = "Указан некорректный id приложения"; + public const string InvalidGenerciRefGroupId = "Указан некорректный id динамического классификатора"; + public const string InvalidGenerciRefGroupKey = "Указан некорректный ключ динамического классификатора"; + public const string InvalidGenerciRefId = "Указан некорректный id элемента динамического классификатора"; + public const string InvalidAttachmentFile = "Отсутствует прикреплённый файл"; + public const string InvalidAttachmentFiles = "Отсутствуют прикреплённые файлы"; + public const string InvalidChecklistIdList = "Отсутствуют элементы в списке id проверочных листов"; + + public const string XmlDoc404Workers = "Документ со списком работников не найден"; + public const string XmlDoc404Checklist = "Документ со структурой проверочного листа не найден"; + public const string XmlDoc404Classifiers = "Документ со структурой классификаторов не найден"; + public const string XmlDoc404CheckupItem = "Документ со структурой проверки не найден"; + public const string XmlDoc404CheckupType = "Документ со списком типов проверок не найден"; + public const string XmlDoc404CheckupCategory = "Документ со списком категорий проверок не найден"; + public const string XmlDoc404CheckupQualityMatrix = "Документ с матрицей оценки проверок не найден"; + + public const string Context404ProjectId = "В контексте работника не указан id проекта"; + public const string Context404VersionId = "В контексте работника не указан id версии"; + public const string Context404WorkerGuid = "В контексте работника не указан id работника"; + + public const string Context403Version = "Работник не имеет доступа к версии проекта"; + public const string Context403Profiles = "Пользователю не доступен ни один вариант входа"; + + public const string User404ByToken = "Пользователь по токену не найден"; + public const string User404ById = "Пользователь по id не найден"; + public const string User404ProfileById = "Профиль пользователя по id не найден"; + + public const string Code500 = "На сервере произошла немзвестная ошибка"; + + /// $"Указан некорректный id {}" + public static string InvalidId(string elementName) + { + return $"Указан некорректный id {elementName}"; + } + + /// $"{} не найден(-а, -о в зависимости от значения )" + public static string Element404(string elementName, WordGender wordGender) + { + string notFoundWord = string.Empty; + switch (wordGender) + { + case WordGender.Masculine: + notFoundWord = "найден"; + break; + case WordGender.Feminine: + notFoundWord = "найдена"; + break; + case WordGender.Neuter: + notFoundWord = "найдено"; + break; + } + + return $"{elementName} не {notFoundWord}"; + } + + public static string Element404CheckupItem = Element404("Экземпляр проверки", WordGender.Masculine); + public static string Element404AttachmentFile = Element404("Файл приложения", WordGender.Masculine); + } +} diff --git a/LibCommon/Kit.Core.Helpers/Exception/WebExceptionAttribute.cs b/LibCommon/Kit.Core.Helpers/Exception/WebExceptionAttribute.cs new file mode 100644 index 0000000..e0d2ab9 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Exception/WebExceptionAttribute.cs @@ -0,0 +1,20 @@ +namespace Kit.Helpers +{ + using System; + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Mvc.Filters; + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] + public class WebExceptionAttribute : ActionFilterAttribute, IExceptionFilter + { + public void OnException(ExceptionContext exceptionContext) + { + if (!exceptionContext.ExceptionHandled) + { + exceptionContext.ExceptionHandled = true; + exceptionContext.HttpContext.Response.WriteException(exceptionContext.Exception); + exceptionContext.Result = new EmptyResult(); + } + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Exception/XmlFormatException.cs b/LibCommon/Kit.Core.Helpers/Exception/XmlFormatException.cs new file mode 100644 index 0000000..3df7fb2 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Exception/XmlFormatException.cs @@ -0,0 +1,53 @@ +using System; + +namespace Kit.Helpers +{ + public class XmlFormatExceptionItem + { + public string Type { get; set; } + public int Line { get; set; } + public int Position { get; set; } + public string Message { get; set; } + } + public class XmlFormatException : Exception + { + public IEnumerable Errors { get; set; } + public XmlFormatException() { } + public XmlFormatException(IEnumerable errors) + { + Errors = errors; + } + } + + public enum ValidateMessageType { Info = 1, Warning = 2, Error = 3 } + + public class XmlValidateMessage + { + private static IDictionary MessageTypeDecoding = new Dictionary() + { + {ValidateMessageType.Info, "Информация"}, + {ValidateMessageType.Warning, "Предупреждение"}, + {ValidateMessageType.Error, "Ошибка"} + }; + + public string DecodedMessageType + { + get + { + string value; + if (MessageTypeDecoding.TryGetValue(MessageType, out value)) return value; + else throw new ArgumentOutOfRangeException("MessageType"); + } + } + public ValidateMessageType MessageType { get; set; } + public string ValidatorName { get; set; } + public string Message { get; set; } + public IEnumerable Parameters { get; set; } + } + public class ParameterValue + { + public string Title { get; set; } + public string Name { get; set; } + public string Value { get; set; } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Extension/Entities/IIdTitle.cs b/LibCommon/Kit.Core.Helpers/Extension/Entities/IIdTitle.cs new file mode 100644 index 0000000..a9699b8 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Extension/Entities/IIdTitle.cs @@ -0,0 +1,152 @@ +using System; + +namespace Kit.Helpers.Extension.Entities +{ + + public interface IIdTitle + { + TId Id { get; set; } + TTitle Title { get; set; } + } + + public interface IIdOnly + { + TId Id { get; set; } + } + + public class IdTitle : IInt32Id, IEquatable, IIdTitle, ICloneable + { + public IdTitle() { } + + public IdTitle(int id, string title) : this() + { + this.Id = id; + this.Title = title; + } + + public virtual int Id { get; set; } + public virtual string Title { get; set; } + + #region Empty + + public static readonly int EmptyId = -1; + public static readonly string EmptyTitle = "n/a"; + + public static TEntity GetEmpty() + where TEntity : IdTitle, new() + { + return new TEntity + { + Id = EmptyId, + Title = EmptyTitle + }; + } + + public static IdTitle GetEmpty() + { + return GetEmpty(); + } + + #endregion + + /// + /// Determines whether the specified is equal to the current . + /// + /// + /// true if the specified is equal to the current ; otherwise, false. + /// + /// The to compare with the current . 2 + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (!(obj is IdTitle)) return false; + return Equals((IdTitle)obj); + } + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + /// An object to compare with this object. + public bool Equals(IdTitle other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return other.Id == Id && Equals(other.Title, Title); + } + + /// + /// Serves as a hash function for a particular type. + /// + /// + /// A hash code for the current . + /// + /// 2 + public override int GetHashCode() + { + unchecked + { + return (Id * 397) ^ (Title != null ? Title.GetHashCode() : 0); + } + } + + public object Clone() + { + return new IdTitle + { + Id = Id, + Title = Title + }; + } + + public static bool operator ==(IdTitle left, IdTitle right) + { + return Equals(left, right); + } + + public static bool operator !=(IdTitle left, IdTitle right) + { + return !Equals(left, right); + } + } + + public class IdTitle : IIdTitle + { + public IdTitle() { } + + public IdTitle(TId id, TTitle title) : this() + { + this.Id = id; + this.Title = title; + } + public IdTitle(IdTitle idTitle) : this() + { + this.Id = idTitle.Id; + this.Title = idTitle.Title; + } + + public virtual TId Id { get; set; } + public virtual TTitle Title { get; set; } + } + + public class IdOnly : IIdOnly + where TId : struct + { + public IdOnly() { } + + public IdOnly(TId id) : this() + { + this.Id = id; + } + + public IdOnly(IdOnly idTitle) : this() + { + this.Id = idTitle.Id; + } + + public virtual TId Id { get; set; } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Extension/Entities/IIdTitleParent.cs b/LibCommon/Kit.Core.Helpers/Extension/Entities/IIdTitleParent.cs new file mode 100644 index 0000000..40478f3 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Extension/Entities/IIdTitleParent.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; + +namespace Kit.Helpers.Extension.Entities +{ + public interface IIdTitleParent + { + string Title { get; set; } + TEntity Parent { get; set; } + int? ParentId { get; set; } + + int Level { get; set; } + bool IsFirst { get; set; } + bool IsLast { get; set; } + + IEnumerable Childs { get; set; } + } + + public class IdTitleParent : IdTitle, IIdTitleParent + { + public int? ParentId { get; set; } + public TEntity Parent { get; set; } + public int Level { get; set; } + public bool IsFirst { get; set; } + public bool IsLast { get; set; } + + public IEnumerable Childs { get; set; } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Extension/Entities/IInt32Id.cs b/LibCommon/Kit.Core.Helpers/Extension/Entities/IInt32Id.cs new file mode 100644 index 0000000..0b5665b --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Extension/Entities/IInt32Id.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace Kit.Helpers.Extension.Entities +{ + public interface IInt32Id + { + int Id { get; } + } + + public static class IInt32IdExtentions + { + public static IDictionary ConvertToDictionary(this IEnumerable objects) + where TObject : IInt32Id + { + var dictionary = new Dictionary(); + { + foreach (var obj in objects) dictionary.Add(obj.Id, obj); + } + return dictionary; + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Extension/Entities/IdParent.cs b/LibCommon/Kit.Core.Helpers/Extension/Entities/IdParent.cs new file mode 100644 index 0000000..6d7a64f --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Extension/Entities/IdParent.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; + +namespace Kit.Helpers.Extension.Entities +{ + public static class IdParentExtension + { + public static string BuildPath(this IIdParent item) + { + if (item != null) + { + return item.Parent.BuildPath().AddSufixIsNotEmpty(".") + item.Id.ToString("x9"); + } + return string.Empty; + } + + public static int BuildLevel(this IIdParent item) + { + if (item != null) + { + return item.Parent.BuildLevel() + 1; + } + return 0; + } + } + + public interface IIdParent + { + int Id { get; set; } + IIdParent Parent { get; set; } + int? ParentId { get; set; } + int Level { get; } + string Path { get; } + IEnumerable Childs { get; set; } + } + + public class IdParent : IIdParent + { + private string _path; + private int _lvl; + public int Id { get; set; } + public IIdParent Parent { get; set; } + public int? ParentId { get; set; } + public int Level + { + get + { + if (_lvl == 0) + { + _lvl = this.BuildLevel(); + } + + return _lvl; + } + } + public IEnumerable Childs { get; set; } + public string Path + { + get + { + if (_path.IsNullOrEmpty()) + { + _path = this.BuildPath(); + } + + return _path; + } + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Extension/Entities/IdTitleExtensions.cs b/LibCommon/Kit.Core.Helpers/Extension/Entities/IdTitleExtensions.cs new file mode 100644 index 0000000..a7e3cbe --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Extension/Entities/IdTitleExtensions.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Mvc.Rendering; +using System.Collections.Generic; +using System.Linq; + +namespace Kit.Helpers.Extension.Entities +{ + public static class IdTitleExtensions + { + public static IEnumerable AddSelectListItem(this IEnumerable selectListItems, string title, string value, bool selected = false, bool insertFirst = true) + { + var selectListItem = new SelectListItem { Text = title, Value = value, Selected = selected }; + + var list = selectListItems as IList + ?? selectListItems.ToList(); + + if (insertFirst) + { + list.Insert(0, selectListItem); + } + else + { + list.Add(selectListItem); + } + + return list; + } + + public static IEnumerable AddSelectListItem(this IEnumerable selectListItems, string title, object value, bool selected = false, bool insertFirst = true) + { + return selectListItems.AddSelectListItem(title, value.ToString(), selected, insertFirst); + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Extension/Entities/ListWithPage.cs b/LibCommon/Kit.Core.Helpers/Extension/Entities/ListWithPage.cs new file mode 100644 index 0000000..f5b5c67 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Extension/Entities/ListWithPage.cs @@ -0,0 +1,42 @@ +namespace Kit.Helpers +{ + public interface IEnumerableWithPage : IEnumerable + { + int TotalRows { get; set; } + int FilteredRows { get; set; } + int PageNo { get; set; } + int PageSize { get; set; } + string Sort { get; set; } + string Dir { get; set; } + } + + public interface IEnumerableWithOffer : IEnumerable + { + int TotalRows { get; set; } + int FilteredRows { get; set; } + int Start { get; set; } + int Lenght { get; set; } + string Sort { get; set; } + string Dir { get; set; } + } + + public class ListWithPage : List, IEnumerableWithPage + { + public int TotalRows { get; set; } + public int FilteredRows { get; set; } + public int PageNo { get; set; } + public int PageSize { get; set; } + public new string Sort { get; set; } + public string Dir { get; set; } + } + + public class ListWithOffer : List, IEnumerableWithOffer + { + public int TotalRows { get; set; } + public int FilteredRows { get; set; } + public int Start { get; set; } + public int Lenght { get; set; } + public new string Sort { get; set; } + public string Dir { get; set; } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Extension/Microsoft.AspNetCore.Http.HttpContext.cs b/LibCommon/Kit.Core.Helpers/Extension/Microsoft.AspNetCore.Http.HttpContext.cs new file mode 100644 index 0000000..0d40dac --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Extension/Microsoft.AspNetCore.Http.HttpContext.cs @@ -0,0 +1,39 @@ +namespace Kit.Helpers +{ + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Routing; + using System.Reflection; + + public static class MicrosoftAspNetCoreHttpHttpContextExtentions + { + public static bool NeedResponseJsonResult(this HttpContext context, Exception? ex = null) + { + var routeData = context.GetRouteData(); + var controllerName = routeData?.Values["controller"]?.ToString(); + var actionName = routeData?.Values["action"]?.ToString(); + + MethodInfo? methodInfo = null; + + if (string.IsNullOrEmpty(controllerName) == false && string.IsNullOrEmpty(actionName) == false) + { + foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + Type? controllerType = assembly.GetTypes()?.FirstOrDefault(t => string.Equals(t.Name, $"{controllerName}Controller", StringComparison.OrdinalIgnoreCase)); + if (controllerType != null) + { + methodInfo = controllerType.GetMethod(actionName); + break; + } + } + } + + if (methodInfo == null && ex?.TargetSite != null && ex.TargetSite is MethodInfo methodInfoEx) + { + methodInfo = methodInfoEx; + } + + return methodInfo != null && methodInfo.ReturnType.Equals(typeof(JsonResult)); + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Extension/Microsoft.AspNetCore.Http.HttpRequest.cs b/LibCommon/Kit.Core.Helpers/Extension/Microsoft.AspNetCore.Http.HttpRequest.cs new file mode 100644 index 0000000..8611e09 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Extension/Microsoft.AspNetCore.Http.HttpRequest.cs @@ -0,0 +1,12 @@ +namespace Kit.Helpers +{ + using Microsoft.AspNetCore.Http; + public static class MicrosoftAspNetCoreHttpHttpRequestExtentions + { + public static string? TryGetEventNameIFrame(this HttpRequest request) + { + string? value = request.Query["eventNameIframe"]; + return string.IsNullOrWhiteSpace(value) ? null : value; + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Extension/Microsoft.AspNetCore.Http.Session.cs b/LibCommon/Kit.Core.Helpers/Extension/Microsoft.AspNetCore.Http.Session.cs new file mode 100644 index 0000000..7fe23d7 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Extension/Microsoft.AspNetCore.Http.Session.cs @@ -0,0 +1,32 @@ +namespace Kit.Helpers +{ + using Microsoft.AspNetCore.Http; + public static class MicrosoftAspNetCoreHttpSessionExtentions + { + public static T GetValue(this ISession session, string key) + { + string json = session.GetString(key); + return json.JsonDeserialize(); + } + + public static T GetValueOrDefault(this ISession session, string key) + { + string json = session.GetString(key); + T result; + try + { + result = json.JsonDeserialize(); + } + catch + { + return default; + } + return result; + } + + public static void SetValue(this ISession session, string key, object obj) + { + session.SetString(key, obj.JsonSerialize()); + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Extension/Microsoft.AspNetCore.Mvc.ViewFeatures.cs b/LibCommon/Kit.Core.Helpers/Extension/Microsoft.AspNetCore.Mvc.ViewFeatures.cs new file mode 100644 index 0000000..fd6e6ee --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Extension/Microsoft.AspNetCore.Mvc.ViewFeatures.cs @@ -0,0 +1,42 @@ +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc.Rendering; +using System.Linq.Expressions; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Kit.Helpers +{ + public static class MicrosoftAspNetCoreMvcViewFeaturesExtension + { + public static IHtmlContent ValidationClassFor(this IHtmlHelper htmlHelper, Expression> expression, string classNameValid = "is-valid", string classNameInvalid = "is-invalid") + { + ArgumentNullException.ThrowIfNull(expression); + string memberName = GetMemberName(expression); + var state = htmlHelper.ViewData.ModelState[memberName]; + + var validationState = state?.ValidationState ?? ModelValidationState.Unvalidated; + + switch (validationState) + { + case ModelValidationState.Invalid: return htmlHelper.Raw(classNameInvalid); + case ModelValidationState.Valid: return htmlHelper.Raw(classNameValid); + default: return htmlHelper.Raw(string.Empty); + } + } + + private static string GetMemberName(Expression> expression) + { + if (expression.Body is MemberExpression memberExpression) + { + // Если выражение представляет доступ к полю или свойству + return memberExpression.Member.Name; + } + else if (expression.Body is UnaryExpression unaryExpression && unaryExpression.Operand is MemberExpression unaryMemberExpression) + { + // Если выражение представляет собой унарную операцию (например, приведение типа) + return unaryMemberExpression.Member.Name; + } + + throw new ArgumentException("Выражение должно представлять доступ к полю или свойству.", nameof(expression)); + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Extension/Microsoft.AspNetCore.Mvc.cs b/LibCommon/Kit.Core.Helpers/Extension/Microsoft.AspNetCore.Mvc.cs new file mode 100644 index 0000000..bfc3730 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Extension/Microsoft.AspNetCore.Mvc.cs @@ -0,0 +1,71 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Routing; +using Kit.Helpers; +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; +using System.Web; + +namespace Kit.Helpers +{ + public static class UrlHelperExtensions + { + public static Func? FuncGetUserToken { get; set; } + public static TModel? FillLayoutModel(this Controller controller, TModel? model) where TModel : LayoutModel + { + model?.Fill(controller.HttpContext, controller.ControllerContext); + return model; + } + + public static string? TryAddExtraData(this string? uriString, bool updateIfExists = true) + { + if (uriString == null) + { + return null; + } + + string? userToken = FuncGetUserToken != null ? FuncGetUserToken() : null; + if (userToken != null) + { + uriString = uriString.SetQueryParam("userToken", userToken, updateIfExists); + } + + string? eventNameIframe = HttpContextCore.Current.Request.TryGetEventNameIFrame(); + if (eventNameIframe != null) + { + uriString = uriString.SetQueryParam("eventNameIframe", eventNameIframe, updateIfExists); + } + + return uriString; + } + + public static string SetQueryParam(this string url, string paramName, string paramValue, bool updateIfExists) + { + // Разделяем URL на путь и query + var parts = url.Split('?'); + var path = parts[0]; + var query = parts.Length > 1 ? parts[1] : ""; + + // Парсим query-параметры + var queryParams = HttpUtility.ParseQueryString(query); + + if (queryParams[paramName] == null || updateIfExists) + { + // Обновляем параметр + queryParams[paramName] = paramValue; + } + + // Собираем URL обратно + var newQuery = queryParams.ToString(); + return path + (string.IsNullOrEmpty(newQuery) ? "" : "?" + newQuery); + } + + public static string? ActionWithExtraData(this IUrlHelper urlHelper, UrlActionContext actionContext) => urlHelper.Action(actionContext).TryAddExtraData(); + public static string? ActionWithExtraData(this IUrlHelper urlHelper, string action, object values) => urlHelper.Action(action, values).TryAddExtraData(); + public static string? ActionWithExtraData(this IUrlHelper urlHelper, string action, string controller) => urlHelper.Action(action, controller).TryAddExtraData(); + public static string? ActionWithExtraData(this IUrlHelper urlHelper, string action, string controller, object values) => urlHelper.Action(action, controller, values).TryAddExtraData(); + public static string? ActionWithExtraData(this IUrlHelper urlHelper, string action, string controller, object values, string? protocol) => urlHelper.Action(action, controller, values, protocol).TryAddExtraData(); + public static string? ActionWithExtraData(this IUrlHelper urlHelper, string action, string controller, object values, string? protocol, string? host) => urlHelper.Action(action, controller, values, protocol, host).TryAddExtraData(); + public static string? ActionWithExtraData(this IUrlHelper urlHelper, string action, string controller, object values, string? protocol, string? host, string? fragment) => urlHelper.Action(action, controller, values, protocol, host, fragment).TryAddExtraData(); + public static string? ActionWithExtraData(this IUrlHelper urlHelper, Expression> actionSelector, object? values = null, string? protocol = null) where TController : Controller => urlHelper.Action(actionSelector, values, protocol).TryAddExtraData(); + } +} diff --git a/LibCommon/Kit.Core.Helpers/Extension/Microsoft.Extensions.Logging.cs b/LibCommon/Kit.Core.Helpers/Extension/Microsoft.Extensions.Logging.cs new file mode 100644 index 0000000..d0c7765 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Extension/Microsoft.Extensions.Logging.cs @@ -0,0 +1,12 @@ +namespace Kit.Helpers +{ + using Microsoft.Extensions.Logging; + public static class MicrosoftExtensionsLoggingExt + { + public static void LogParamTrace(this ILogger logger, string method, KeyValuePair param) => LogParamsTrace(logger, method, [param]); + public static void LogParamsTrace(this ILogger logger, string method, IEnumerable> @params) + { + logger.LogTrace(method + Environment.NewLine + @params.Select(x => $"param \"{x.Key}\": {(x.Value?.JsonSerialize(enableCyrillic: true, writeIntended: true) ?? "")}").Join(Environment.NewLine)); + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Extension/System.Boolean.cs b/LibCommon/Kit.Core.Helpers/Extension/System.Boolean.cs new file mode 100644 index 0000000..81f151d --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Extension/System.Boolean.cs @@ -0,0 +1,10 @@ +namespace Kit.Helpers +{ + public static partial class SystemBooleanExtensionMethods + { + public static string ToJsString(this bool value) + { + return value ? "true" : "false"; + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Extension/System.Collections.cs b/LibCommon/Kit.Core.Helpers/Extension/System.Collections.cs new file mode 100644 index 0000000..cb00e6b --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Extension/System.Collections.cs @@ -0,0 +1,304 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Xml.Linq; + +namespace Kit.Helpers +{ + public static class SystemCollectionExtensionMethods + { + public static IList NullToEmpty(this IList list) + { + return list ?? new List(); + } + + public static IEnumerable NullToEmpty(this IEnumerable list) + { + return list ?? new List(); + } + + public static IList AddRange(this IList list, IEnumerable items) + { + if (items == null) return list; + foreach (TItem item in items) list.Add(item); + return list; + } + + public static IList AddRangeEx(this IList list, IEnumerable items) + { + if (items == null) return list; + foreach (TItem item in items) list.Add(item); + return list; + } + + public static IList AddEx(this IList list, TItem item) + { + list.Add(item); + return list; + } + + public static IEnumerable AddToNewList(this IEnumerable list, TItem item) + { + return list.ToList().AddEx(item); + } + + public static IList AddToNewList(this IList list, TItem item) + { + return list.AddEx(item); + } + + public static IDictionary AddRange(this IDictionary target, IDictionary source) + { + foreach (var item in source) target.SetByKey(item.Key, item.Value); + return target; + } + + public static bool IsNullOrEmpty([NotNullWhen(false)] this IEnumerable? collection) + { + return collection == null || collection.Any() == false; + } + + public static TValue GetByKey(this IDictionary dictionary, TKey key, TValue defaultValue) + { + return dictionary != null && dictionary.ContainsKey(key) + ? dictionary[key] + : defaultValue; + } + + public static TValue GetByKey(this IDictionary dictionary, TKey key) + { + return dictionary.GetByKey(key, default(TValue)); + } + + public static TValue SetByKey(this IDictionary dictionary, TKey key, TValue value) + { + if (dictionary == null) throw new ArgumentNullException("dictionary"); + lock (dictionary) + { + if (!dictionary.ContainsKey(key)) dictionary.Add(key, value); + else dictionary[key] = value; + } + return value; + } + + public static object CastToTypedEnumerable(this IEnumerable collection, Type targetType) + { + var resultListType = typeof(List<>).MakeGenericType(targetType); + var resultList = (IList)Activator.CreateInstance(resultListType); + foreach (var collectionItem in collection) resultList.Add(collectionItem); + return resultList; + } + + public static Dictionary ParseToDictionaryString(this NameValueCollection collection) + { + Dictionary result = new Dictionary(); + foreach (string item in collection.Keys) + { + result.Add(item, collection[item]); + } + return result; + } + + public static string ParseToString(this NameValueCollection collection, char parameterSeparator, char nameValueSeparator) + { + StringBuilder sb = new StringBuilder(); + foreach (string item in collection.Keys) + { + sb.AppendFormat("{0}{1}{2}{3}",item,nameValueSeparator,collection[item],parameterSeparator); + } + return sb.ToString(); + } + + public static void Add(this NameValueCollection collection, IEnumerable> items) + { + foreach (KeyValuePair kv in items) + { + if (Equals(kv.Key, default(TKey))) continue; + string value = Equals(kv.Value, default(TValue)) ? string.Empty : kv.Value.ToString(); + collection.Add(kv.Key.ToString(), value); + } + } + + public static NameValueCollection ToNameValueCollection(this IEnumerable> kvpCollection) + { + NameValueCollection collection = new NameValueCollection(); + collection.Add(kvpCollection); + return collection; + } + public static string Join(this string[] array, string separator) + { + return string.Join(separator, array); + } + public static string Join(this string[] array) + { + return array.Join(","); + } + public static string Join(this IEnumerable collection, string separator) + { + if (collection == null) return null; + IEnumerable stringCollection = collection.Select(x => x.ToString()); + + + //(from o in collection + // where Equals(o, default(TObject)) + // select o.ToString()); + return stringCollection.ToArray().Join(separator); + } + public static string Join(this IEnumerable collection) + { + return collection.Join(","); + } + public static IEnumerable> ToKeyValuePairs(this NameValueCollection nvc) + { + List> kvc = new List>(); + if (nvc == null) return kvc; + foreach(string key in nvc.Keys) kvc.Add(new KeyValuePair(key, nvc[key])); + return kvc; + } + public static IEnumerable Random(this IEnumerable collection, int limit = -1) + { + if (collection == null) return null; + var random = new Random(); + collection = collection.OrderBy(o => random.Next()); + if (limit > -1) collection = collection.Take(limit); + return collection; + } + + public static IList ForEach(this IList collection, Action action) + { + if (collection == null) return null; + if (action == null) return collection; + + foreach (T element in collection) action(element); + return collection; + } + + public static IEnumerable ForEach(this IEnumerable collection, Action action) + { + if (collection == null) return null; + if (action == null) return collection; + IEnumerable forEach = collection as IList ?? collection.ToList(); + foreach (T element in forEach) action(element); + return forEach; + } + + public static T[] ForEach(this T[] collection, Action action) + { + return ((IEnumerable)collection).ForEach(action).ToArray(); + } + + public static IEnumerable ForEach(this IEnumerable collection, Action action) + { + if (collection == null) return null; + if (action == null) return collection; + IEnumerable forEach = collection as IList ?? collection.ToList(); + + int indexEnd = forEach.Count(); + for (int index = 0; index < indexEnd; index++) + { + T element = forEach.ElementAt(index); + action(element, index); + } + return forEach; + } + + public static IEnumerable InsertFirstToNewList(this IEnumerable list, TItem item) + { + var lists = list.ToList(); + lists.Insert(0, item); + return lists; + } + + public static IEnumerable InsertEx(this IList list, int index, TItem item) + { + list.Insert(index, item); + return list; + } + + + public static List SearchBytePattern(this byte[] Source, byte[] Pattern, int Start) + { + int SourceLen = Source.Length - Pattern.Length + 1;//Получаем длину исходного массива. + int j; + var positions = new List();//Переменная для хранения списка результатов поиска. + + for (int i = Start; i < SourceLen; i++) + { + if (Source[i] != Pattern[0]) continue;//Сравниваем первый искомый байт и если он совпадает, то проверяем остальные байты за ним идущие, иначе идем дальше по массиву. + for (j = Pattern.Length - 1; j >= 1 && Source[i + j] == Pattern[j]; j--) ;//Сравниваем остальные байты с нашим значением. + if (j == 0)//Переменная будет равна нулю, если все байты совпали + { + positions.Add(i);//Добавляем адрес конца совпадения первого байта в список + i += Pattern.Length - 1;//Увеличиваем значение переменной на величину искомого, так как мы его уже проверили и это ускоряет поиск. + } + } + return positions;//Отдаем список адресов, которые совпадают с искомым значением. + } + + public static IEnumerable> GetPages(this IEnumerable list, int pageSize) + { + int pageCount = (int)Math.Ceiling((list.Count() * 1.0) / (pageSize * 1.0)); + + var lists = new List>(); + + for (int pageNum = 0; pageNum < pageCount; pageNum++) + { + lists.Add(list.Skip(pageNum * pageSize).Take(pageSize).ToList()); + } + + return lists; + } + + public static IEnumerable GetPage(this IEnumerable list, int page, int pageSize) + { + return list.Skip(page * pageSize).Take(pageSize); + } + + public static void AddDistinctValue(this IDictionary> dictionary, K key, params TValue[] newValue) + { + AddDistinctValue(dictionary, key, comparer: null, newValue: newValue); + } + public static void AddDistinctValue(this IDictionary> dictionary, K key, IEqualityComparer comparer, params TValue[] newValue) + { + if (dictionary.ContainsKey(key)) + { + List values = dictionary[key].ToList(); + + if (values.Any(x => x.Equals(newValue)) == false) + { + IEnumerable newValues = comparer == null ? values.Union(newValue).ToList() : values.Union(newValue, comparer).ToList(); + dictionary[key] = newValues; + } + } + else + { + dictionary.Add(key, newValue.ToList()); + } + } + + public static void AddListValue(this IDictionary> dictionary, K key, params TValue[] newValue) + { + if (dictionary.ContainsKey(key)) + { + List values = dictionary[key].ToList(); + values.AddRange(newValue); + dictionary[key] = values; + } + else + { + dictionary.Add(key, newValue.ToList()); + } + } + + public static bool SetEquals(this IEnumerable firstCollection, IEnumerable secondCollection) + { + if (firstCollection == null && secondCollection == null) return true; + if (firstCollection == null || secondCollection == null) return false; + return new HashSet(firstCollection).SetEquals(new HashSet(secondCollection)); + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Extension/System.DateTime.cs b/LibCommon/Kit.Core.Helpers/Extension/System.DateTime.cs new file mode 100644 index 0000000..3261c04 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Extension/System.DateTime.cs @@ -0,0 +1,172 @@ +using System.Globalization; + +namespace Kit.Helpers +{ + public class WeekInfo + { + public int NumberOfYear { get; set; } + public DateTime StartDate { get; set; } + public DateTime EndDate { get; set; } + } + public static partial class SystemDateTimeExtensionMethods + { + public static string ToStringDateRus(this DateTime dt) + { + return dt.ToString("dd.MM.yyyy"); + } + + public static string ToStringDateTimeRus(this DateTime dt) + { + return dt.ToString("dd.MM.yyyy hh:mm"); + } + + public static string ToStringTimeRus(this DateTime dt) + { + return dt.ToString("hh:mm:ss"); + } + + + public static string ToStringDateRus(this DateTime? dt) + { + if (dt == null) return String.Empty; + return dt.ToStringDateRus(); + } + + public static string ToStringDateTimeRus(this DateTime? dt) + { + if (dt == null) return String.Empty; + return dt.Value.ToStringDateTimeRus(); + } + + public static string ToStringTimeRus(this DateTime? dt) + { + if (dt == null) return String.Empty; + return dt.Value.ToStringTimeRus(); + } + + private static CultureInfo cultureRu = new CultureInfo("ru-Ru"); + public static string GetStringMonthYear(this DateTime date) + { + return date.ToString("Y"); + } + //public static DateTime Normolize(this DateTime currentValue) + //{ + // return currentValue.Normolize(DateTime.MinValue, DateTime.Now); + //} + + public static DateTime GetClearTime(this DateTime date) + { + return new DateTime(date.Year, date.Month, date.Day); + } + + private static readonly long DatetimeMinTimeTicks = + (new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).Ticks; + + public static long ToJavaScriptMilliseconds(this DateTime dt) + { + return (long)((dt.ToUniversalTime().Ticks - DatetimeMinTimeTicks) / 10000); + } + + + public static DateTime GetUtcFromDateTime(this DateTime dt, string timezene) + { + TimeZoneInfo tzf; + try + { + tzf = TimeZoneInfo.FindSystemTimeZoneById(timezene); + } + catch + { + tzf = TimeZoneInfo.FindSystemTimeZoneById("Russian Standard Time"); + } + return TimeZoneInfo.ConvertTimeToUtc(dt, tzf); + } + + public static DateTime GetDateTimeFromUtc(this DateTime dt, string timezene) + { + TimeZoneInfo tzf; + try + { + tzf = TimeZoneInfo.FindSystemTimeZoneById(timezene); + } + catch + { + tzf = TimeZoneInfo.FindSystemTimeZoneById("Russian Standard Time"); + } + return TimeZoneInfo.ConvertTimeFromUtc(dt, tzf); + } + + + public static string ToStringOrEmpty(this DateTime? dt, string format) + { + if (dt == null) return String.Empty; + return dt.Value.ToString(format); + } + + public static string ToStringOrEmpty(this DateTime? dt) + { + if (dt == null) return String.Empty; + return dt.Value.ToString("dd.MM.yyyy"); + } + + public static string ToShortDateStringRu(this DateTime value) + { + return value.ToString("dd.MM.yyyy"); + } + + public static string ToShortDateStringUniversal(this DateTime value) + { + return value.ToString("yyyy.MM.dd"); + } + + public static string ToStringDMYHMS(this DateTime value) + { + return value.ToString("dd.MM.yyyy hh:mm:ss"); + } + + public static string ToShortDateStringRu(this DateTime? value) + { + if (value == null) return String.Empty; + return value.Value.ToShortDateStringRu(); + } + + public static string ToShortDateStringUniversal(this DateTime? value) + { + if (value == null) return String.Empty; + return value.Value.ToShortDateStringUniversal(); + } + + public static string ToStringDMYHMS(this DateTime? value) + { + if (value == null) return String.Empty; + return value.Value.ToStringDMYHMS(); + } + + public static WeekInfo GetInfoWeek(this DateTime? value) + { + if (!value.HasValue) return null; + return value.Value.GetInfoWeek(); + } + + public static WeekInfo GetInfoWeek(this DateTime value) + { + var result = new WeekInfo(); + DateTimeFormatInfo dfi = cultureRu.DateTimeFormat; + Calendar cal = dfi.Calendar; + + result.NumberOfYear = cal.GetWeekOfYear(value, CalendarWeekRule.FirstFullWeek, DayOfWeek.Monday); + + var dtStart = new DateTime(value.Year, 1, 1).AddDays(-(int)(new DateTime(value.Year, 1, 1).DayOfWeek - 1)); + result.StartDate = dtStart.AddDays((result.NumberOfYear - 1) * 7); + result.EndDate = dtStart.AddDays(result.NumberOfYear * 7); + + return result; + } + + public static string GetMonthNameRP(this DateTime dt) + { + return dt.ToString("MMMMMMMMMMM", cultureRu).Replace("т", "та").Replace("тая", "тя").Replace("й", "я").Replace("ь", "я"); + } + + } +} diff --git a/LibCommon/Kit.Core.Helpers/Extension/System.IO.Stream.cs b/LibCommon/Kit.Core.Helpers/Extension/System.IO.Stream.cs new file mode 100644 index 0000000..0d12609 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Extension/System.IO.Stream.cs @@ -0,0 +1,54 @@ +using System.IO; +using System.IO.Pipes; +using System.Reflection; +using System.Security.Cryptography; + +namespace Kit.Helpers +{ + public static class SystemIOStreamExtentions + { + public static byte[] ReadFromStream(this Stream stream) + { + byte[] array = new byte[32768]; + using (MemoryStream memoryStream = new MemoryStream()) + { + + while (true) + { + int num = stream.Read(array, 0, array.Length); + if (num <= 0) + { + break; + } + + memoryStream.Write(array, 0, num); + } + + return memoryStream.ToArray(); + } + } + + public static string ComputeHash(this Stream stream) where THashAlgorithm : HashAlgorithm + { + // Получаем тип THashAlgorithm + Type algorithmType = typeof(THashAlgorithm); + + // Получаем метод Create() через Reflection + MethodInfo? createMethod = algorithmType.GetMethods(BindingFlags.Public | BindingFlags.Static).SingleOrDefault(x => x.Name == "Create" && x.GetParameters().Length == 0); + + // Проверяем, существует ли метод + if (createMethod == null) + { + throw new InvalidOperationException($"Тип {algorithmType.FullName} не имеет статического метода Create()."); + } + + // Вызываем метод Create() и приводим результат к HashAlgorithm + using (var algorithm = (THashAlgorithm)createMethod.Invoke(null, null)) + { + var hash = BitConverter.ToString(algorithm.ComputeHash(stream)).Replace("-", "").ToLowerInvariant(); + if (stream.CanSeek) stream.Position = 0; + return hash; + } + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Extension/System.Integer.cs b/LibCommon/Kit.Core.Helpers/Extension/System.Integer.cs new file mode 100644 index 0000000..f9a80e3 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Extension/System.Integer.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace Kit.Helpers +{ + public static class SystemIntegerExtensionMethods + { + public static string ToMonthName(this int monthNo) + { + if (monthNo < 1 || monthNo > 12) { throw new ArgumentOutOfRangeException("monthNo", monthNo, "monthNo must be one of 1..12"); } + var dateTimeFormat = new CultureInfo("ru-RU").DateTimeFormat; + return dateTimeFormat.GetMonthName(monthNo); + } + public static string ToMonthNamea(this int monthNo) + { + if (monthNo < 1 || monthNo > 12) { throw new ArgumentOutOfRangeException("monthNo", monthNo, "monthNo must be one of 1..12"); } + var monthNames = new string[] { + "января", "февраля", "марта", "апреля", "мая", "июня", "июля", "августа", "сентября", "октября", "ноября", "декабря" + }; + return monthNames[monthNo - 1]; + } + + + public static string Join(this IEnumerable collection, string separator) + { + if (collection == null) return null; + IEnumerable stringCollection = collection.Select(x => x.ToString()); + return stringCollection.ToArray().Join(separator); + } + + public static bool InRange(this int value, int rangeStart, int rangeEnd) + { + return value >= rangeStart && value <= rangeEnd; + } + + public static IEnumerable Select(this int count, Func func) + { + var list = new List(); + for (int i = 0; i < count; i++) + { + list.Add(func(i)); + } + + return list; + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Extension/System.Object.cs b/LibCommon/Kit.Core.Helpers/Extension/System.Object.cs new file mode 100644 index 0000000..a8d22ee --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Extension/System.Object.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.Linq; +using System.Security.Cryptography; + +namespace Kit.Helpers +{ + public static partial class SystemObjectExtensionMethods + { + public static string ToJSON(this object obj, bool enableCyrillic = false) + { + return obj.JsonSerialize(enableCyrillic); + } + + + public static TObject ToObjectFromJSON(this string json) + { + return json.JsonDeserialize(); + } + + + + public static void SetProperties(this object obj, NameValueCollection properties) + { + Type objectType = obj.GetType(); + foreach (string name in properties.Keys) + { + var property = objectType.GetProperty(name); + object propertyValue = property.PropertyType.IsEnum + ? Enum.Parse(property.PropertyType, properties[name]) + : System.Convert.ChangeType(properties[name], property.PropertyType); + property.SetValue(obj, propertyValue, null); + } + } + + public static void SetProperties(this object obj, Dictionary properties) + { + Type objectType = obj.GetType(); + foreach (string name in properties.Keys) + { + var property = objectType.GetProperty(name); + object propertyValue = properties[name]; + property.SetValue(obj, propertyValue, null); + } + } + + public static object GetPropertyValue(this object thisObject, string propertyPath) + { + if (thisObject == null) return null; + + string[] propertyNames = propertyPath.Split('.'); + foreach (var propertyName in propertyNames) + { + var propertyInfo = thisObject.GetType().GetProperty(propertyName); + if (propertyInfo == null) throw new ArgumentOutOfRangeException("PropertyName", propertyName, "Property not found."); + thisObject = propertyInfo.GetValue(thisObject, null); + } + return thisObject; + } + + public static T Normolize(this T currentValue, T minValue, T defaultValue) + where T : IComparable + { + return currentValue.CompareTo(minValue) > 0 ? currentValue : defaultValue; + } + + public static T CreateClone(this T thisObject) + where T : ICloneable + { + return (T)thisObject.Clone(); + } + + public static IEnumerable AscendantsOrSelf(this T obj, Func GetParent) where T : class + { + if (obj == null) throw new ArgumentNullException("obj"); + if (GetParent == null) throw new ArgumentNullException("GetParent"); + + yield return obj; + T parent = GetParent(obj); + while (parent != null) + { + yield return parent; + parent = GetParent(parent); + } + } + + public static string ToStringOrEmpty(this object obj) + { + if (obj == null) return String.Empty; + return obj.ToString(); + } + + /// + /// Шифрует исходное сообщение AES ключом (добавляет соль) + /// + /// + /// + public static byte[] ToAes256(this string src, byte[] aeskey) + { + //Объявляем объект класса AES + Aes aes = Aes.Create(); + //Генерируем соль + aes.GenerateIV(); + //Присваиваем ключ. aeskey - переменная (массив байт), сгенерированная методом GenerateKey() класса AES + aes.Key = aeskey; + byte[] encrypted; + ICryptoTransform crypt = aes.CreateEncryptor(aes.Key, aes.IV); + using (MemoryStream ms = new MemoryStream()) + { + using (CryptoStream cs = new CryptoStream(ms, crypt, CryptoStreamMode.Write)) + { + using (StreamWriter sw = new StreamWriter(cs)) + { + sw.Write(src); + } + } + //Записываем в переменную encrypted зашиврованный поток байтов + encrypted = ms.ToArray(); + } + //Возвращаем поток байт + крепим соль + return encrypted.Concat(aes.IV).ToArray(); + } + + public static string FromAes256(this byte[] shifr, byte[] aeskey) + { + byte[] bytesIv = new byte[16]; + byte[] mess = new byte[shifr.Length - 16]; + //Списываем соль + for (int i = shifr.Length - 16, j = 0; i < shifr.Length; i++, j++) + bytesIv[j] = shifr[i]; + //Списываем оставшуюся часть сообщения + for (int i = 0; i < shifr.Length - 16; i++) + mess[i] = shifr[i]; + //Объект класса Aes + Aes aes = Aes.Create(); + //Задаем тот же ключ, что и для шифрования + aes.Key = aeskey; + //Задаем соль + aes.IV = bytesIv; + //Строковая переменная для результата + string text = ""; + byte[] data = mess; + ICryptoTransform crypt = aes.CreateDecryptor(aes.Key, aes.IV); + using (MemoryStream ms = new MemoryStream(data)) + { + using (CryptoStream cs = new CryptoStream(ms, crypt, CryptoStreamMode.Read)) + { + using (StreamReader sr = new StreamReader(cs)) + { + //Результат записываем в переменную text в вие исходной строки + text = sr.ReadToEnd(); + } + } + } + return text; + + } + + public static byte[] ReadAllBytes(this Stream instream) + { + if (instream is MemoryStream) + return ((MemoryStream)instream).ToArray(); + + using (var memoryStream = new MemoryStream()) + { + instream.CopyTo(memoryStream); + + return memoryStream.ToArray(); + } + } + + public static string ReadFileAes256(this Stream stream,byte[] aeskey) + { + return ReadAllBytes(stream).FromAes256(aeskey); + } + + public static void WriteFileAes256(this Stream stream, string data, byte[] aeskey) + { + byte[] dataBytes = data.ToAes256(aeskey); + stream.Write(dataBytes, 0, dataBytes.Length); + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Extension/System.String.cs b/LibCommon/Kit.Core.Helpers/Extension/System.String.cs new file mode 100644 index 0000000..a3eb9c6 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Extension/System.String.cs @@ -0,0 +1,1785 @@ +using Microsoft.Extensions.Primitives; +using System.Collections.Specialized; +using System.Data; +using System.Globalization; +using System.IO.Compression; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; + +namespace Kit.Helpers +{ + using Microsoft.AspNetCore.Http; + using System; + using static System.Net.Mime.MediaTypeNames; + + public enum GenerateTokenAlphabetMode + { + None = 0, + AnyCase = 1, + LowerCase = 2, + UpperCase = 3, + } + + public enum GenerateTokenDigitMode + { + None = 0, + Dec = 1, + Hex = 2 + } + + public static partial class SystemStringExtensionMethods + { + private static readonly CultureInfo ci = new CultureInfo("en-US"); + + #region преобразования строк + /// + /// Вставляем подстроку в сроку + /// + /// исходная строка + /// куда вставлять + /// строка для вставки + /// количество повторений при вставке + /// преобразованная строка + public static string Insert(this string thisString, int startIndex, string value, int count) + { + StringBuilder sb = new StringBuilder(thisString); + sb.Insert(startIndex, value, count); + return sb.ToString(); + } + /// + /// Применяем функцию String.Format к текущей строке + /// + /// строка с форматом + /// аргументы преобразования + /// преобразованная строка + public static string ApplyFormat(this string formatString, params object[] args) + { + if (formatString.IsNullOrEmpty()) + return String.Empty; + return String.Format(formatString, args); + } + /// + /// Применяем функция String.Format к параметру, используя текущую строку как аргумент + /// + /// текущая строка - аргумент операции + /// строка форматирования + /// преобразованная строка + public static string ApplyToFormat(this string thisString, string formatString) + { + return String.Format(formatString, thisString); + } + /// + /// Проверка строки на null или пустое значение + /// + /// Строка для проверки + /// true - если строка null или пустая, иначе false + public static bool IsNullOrEmpty(this string thisString) + { + return String.IsNullOrEmpty(thisString); + } + /// + /// Замена исходной строки на другую, если она пустая или null + /// + /// Проверяемая строка + /// Строка для замены + /// строка для замены, если исходная строка равна null или пустая, иначе исходная строка + public static string IfNullOrEmpty(this string thisString, string otherString) + { + return !String.IsNullOrEmpty(thisString) ? thisString : otherString; + } + /// + /// Замена исходной строки на другую, если она пустая или null + /// + /// Проверяемая строка + /// Строка для замены + /// строка для замены, если исходная строка равна null или пустая, иначе исходная строка + public static string IfNullOrWhiteSpace(this string thisString, string otherString) + { + return !String.IsNullOrWhiteSpace(thisString) ? thisString : otherString; + } + /// + /// Удаление подстроки + /// + /// Строка в которой идет поиск + /// Подстрока, которую необходимо удалить + /// Преобразованная строка + public static string? Remove(this string? input, string remove) + { + if (input == null) return null; + return input.Replace(remove, String.Empty); + } + + /// + /// Обрезка строки по символу. + /// + /// Строка которую обрезаем + /// Максимальная длинна строки + /// Максимальная длинна строки + /// Обрезанная строка + public static string CutString(this string input, int lenght, char end) + { + string result = input; + if (lenght < input.Length) + { + string tmpstr = input.Substring(0, lenght); + string[] arrstr = tmpstr.Split(end); + if (lenght > 1) + result = arrstr.Take(arrstr.Length - 1).ToArray().Join(end.ToString()) + end.ToString(); + } + return result; + } + #endregion + + #region Методы для перечислений + /// + /// Разбираем строку в элемент перечисления + /// + /// тип перечисления + /// строка + /// игнорировать регистр + /// типизированный элемент перечисления + public static T ParseToEnum(this string value, bool ignoreCase) + { + return (T)Enum.Parse(typeof(T), value.Trim(), ignoreCase); + } + /// + /// Разбираем строку в элемент перечисления + /// с учетом регистра + /// + /// тип перечисления + /// строка + /// типизированный элемент перечисления + public static T ParseToEnum(this string value) + { + return value.ParseToEnum(true); + } + + public static T ParseToEnum(this string value, bool ignoreCase, T defaultValue) + { + try + { + return (T)Enum.Parse(typeof(T), value.Trim(), ignoreCase); + } + catch (Exception) + { + return defaultValue; + } + } + + #endregion + + #region Virtual Path Utility + /* + /// + /// Заменяем пути относительные приложения на пути относительные сервера + /// + /// текст со ссылками + /// текст с измененными ссылками + public static string TextUrlsToAbsolute(this string text) + { + string basePath = VirtualPathUtility.ToAbsolute("~/"); + return text.Replace("~/", basePath); + } + */ + + /* + /// + /// Заменяем пути к текущей теме на реальные. + /// Заменяем пути относительные приложения на пути относительные сервера + /// + /// текст со ссылками + /// имя текущей темы + /// текст с измененными ссылками + public static string TextThemeUrlsToAbsolute(this string text, string theme) + { + string themePath = "~/App_Themes/{0}/".ApplyFormat(theme); + return text.Replace("~/CurrentTheme/", themePath).TextUrlsToAbsolute(); + } + */ + #endregion + + #region StringBuilder + + public static StringBuilder AppendFormatLine(this StringBuilder sb, string formatString, params object[] args) + { + if (!formatString.IsNullOrEmpty()) sb.AppendLine(formatString.ApplyFormat(args)); + return sb; + } + + #endregion + + + public static int ParseIPToInt32(this string ipString) + { + int ipInt32 = 0; + int q = 1; + foreach (string part in ipString.Split('.').Reverse()) + { + ipInt32 += Int32.Parse(part) * q; + q *= 256; + } + return ipInt32; + } + + public static string? CleanFromHtmlSymbols(this string? text) + { + if (text == null) + { + return null; + } + + text = text.Replace("&", "&"); + text = text.Replace(" ", " "); + text = text.Replace("«", "\""); + text = text.Replace("»", "\""); + text = text.Replace("–", "-"); + text = text.Replace("―", "-"); + return text; + } + + public static string CleanFromHtmlTags(this string text) + { + text.CleanFromHtmlSymbols(); + + text = Regex.Replace(text, @"\x26\S*?\x3B", " ", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled);//&...; + text = Regex.Replace(text, @"\<\s*?p\s*?.*?\>", " ", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled);//

+ text = Regex.Replace(text, @"\<\s*?br\s*?.*?\>", "\n", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled);//

+ text = Regex.Replace(text, @"\<(.+?)\>", " ", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled);// <...> + text = Regex.Replace(text, @"\", " ", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled);// + return text; + } + + public static string ToFirstSymbolUppper(this string input) + { + string output = string.Empty; + if (input != null && input.Length > 0) + { + output = input.Substring(0, 1).ToUpper(); + if (input.Length > 1) output += input.Substring(1, input.Length - 1); + } + return output; + } + + public static string ToFirstSymbolUppperOnly(this string input) + { + string output = string.Empty; + if (input != null && input.Length > 0) + { + output = input.Substring(0, 1).ToUpper(); + } + return output; + } + + + #region преобразования типов + + public static Int32? TryParseToInt32Nulable(this string input) + { + Int32 intValue; + if (!Int32.TryParse(input, out intValue)) return null; + return intValue; + } + + public static int TryParseToInt32(this string input) + { + int intValue = 0; + int.TryParse(input, out intValue); + return intValue; + } + + public static int TryParseToInt32(this string? input, int defaultValue) + { + int result; + if (int.TryParse(input, out result)) return result; + return defaultValue; + } + + public static Int32? TryParseToInt32Nulable(this StringValues input) + { + Int32 intValue; + if (!Int32.TryParse(input, out intValue)) return null; + return intValue; + } + + public static Int32 TryParseToInt32(this StringValues input) + { + Int32 intValue = 0; + Int32.TryParse(input, out intValue); + return intValue; + } + + public static Int32 TryParseToInt32(this StringValues input, Int32 defaultValue) + { + int result; + if (Int32.TryParse(input, out result)) return result; + return defaultValue; + } + + public static UInt32 TryParseToUInt32(this string input) + { + UInt32 intValue = 0; + UInt32.TryParse(input, out intValue); + return intValue; + } + public static UInt32 TryParseToUInt32(this string input, UInt32 defaultValue) + { + uint result; + if (UInt32.TryParse(input, out result)) return result; + return defaultValue; + } + public static UInt64 TryParseToUInt64(this string input) + { + ulong intValue = 0; + UInt64.TryParse(input, out intValue); + return intValue; + } + public static UInt64 TryParseToUInt64(this string input, UInt64 defaultValue) + { + ulong result; + if (UInt64.TryParse(input, out result)) return result; + return defaultValue; + } + public static UInt64? TryParseToUInt64Nulable(this string input) + { + UInt64 intValue; + if (!UInt64.TryParse(input, out intValue)) return null; + return intValue; + } + public static Decimal TryParseToDecimal(this string input, Decimal defaultValue) + { + Decimal.TryParse(input, out defaultValue); + return defaultValue; + } + + public static Decimal TryParseToDecimal(this string input) + { + Decimal decimalValue = 0; + Decimal.TryParse(input, out decimalValue); + return decimalValue; + } + + public static Decimal TryParseToDecimal(this string input, IFormatProvider fmtPrvdr, Decimal defaultValue) + { + Decimal.TryParse(input, NumberStyles.Number, fmtPrvdr, out defaultValue); + return defaultValue; + } + + public static Double TryParseToDouble(this string input, Double defaultValue) + { + Double.TryParse(input, out defaultValue); + return defaultValue; + } + + public static Double TryParseToDouble(this string input) + { + Double decimalValue = 0; + Double.TryParse(input, out decimalValue); + return decimalValue; + } + + public static Double TryParseToDouble(this string input, IFormatProvider fmtPrvdr, Double defaultValue) + { + Double.TryParse(input, NumberStyles.Number, fmtPrvdr, out defaultValue); + return defaultValue; + } + + public static IEnumerable ParseToInt32Collection(this string input) + { + return input.ParseToInt32Collection(","); + } + public static IEnumerable ParseToInt32Collection(this string input, string separator) + { + if (input.IsNullOrEmpty()) return new List(); + return input + .Split(separator.ToCharArray()) + .Select(str => str.TryParseToInt32Nulable()) + .Where(intValue => intValue != null) + .Select(intValue => intValue ?? 0) + .ToList(); + } + + public static IEnumerable ParseToShortCollection(this string input) + { + return input.ParseToShortCollection(","); + } + public static IEnumerable ParseToShortCollection(this string input, string separator) + { + if (input.IsNullOrEmpty()) return new List(); + return input + .Split(separator.ToCharArray()) + .Select(str => str.TryParseToShort()) + .ToList(); + } + + public static short TryParseToShort(this string input) + { + short intValue = 0; + short.TryParse(input, out intValue); + return intValue; + } + + public static Int64 TryParseToShort(this string input, short defaultValue) + { + short.TryParse(input, out defaultValue); + return defaultValue; + } + + public static Int64? TryParseToInt64Nulable(this string input) + { + Int64 intValue; + if (!Int64.TryParse(input, out intValue)) return null; + return intValue; + } + + public static Int64 TryParseToInt64(this string input) + { + Int64 intValue = 0; + Int64.TryParse(input, out intValue); + return intValue; + } + + public static long TryParseToInt64(this string? input, long defaultValue) + { + if (long.TryParse(input, out long value)) + { + return value; + } + return defaultValue; + } + + public static IEnumerable ParseToInt64Collection(this string input) + { + return input.ParseToInt64Collection(","); + } + public static IEnumerable ParseToInt64Collection(this string input, string separator) + { + if (input.IsNullOrEmpty()) return new List(); + return input + .Split(separator.ToCharArray()) + .Select(str => str.TryParseToInt64Nulable()) + .Where(intValue => intValue != null) + .Select(intValue => intValue ?? 0); + } + + public static NameValueCollection ParseToNameValueCollection(this string input, char parameterSeparator, char nameValueSeparator) + { + NameValueCollection collection = new NameValueCollection(); + if (!input.IsNullOrEmpty()) + foreach (string item in input.Split(parameterSeparator)) + { + string[] parameter = item.Split(nameValueSeparator); + if (parameter.Length == 2) + { + collection.Add(parameter[0], parameter[1]); + } + } + return collection; + } + + public static NameValueCollection ParseToNameValueCollection(this string input) + { + return input.ParseToNameValueCollection(';', ':'); + } + + public static IEnumerable ParseToSplitCollection(this string value, char parameterSeparator) + { + if (value.IsNullOrEmpty()) return new List(); + return value.Split(parameterSeparator); + } + + public static DateTime? TryParseToDateTimeNullable(this string value) + { + return value.TryParseToDateTimeNullable(null); + } + + public static DateTime? TryParseToDateTimeNullable(this string value, DateTime? defaultValue) + { + DateTime td; + if (DateTime.TryParse(value, out td)) return td; + return defaultValue; + } + + public static DateTime TryParseToDateTimeUTC(this string value, DateTime defaultValue) + { + DateTime td; + var culture = CultureInfo.CreateSpecificCulture("ru-Ru"); + var styles = DateTimeStyles.AdjustToUniversal; + + if (DateTime.TryParse(value, culture, styles, out td)) return td; + return defaultValue; + } + public static DateTime TryParseToDateDMYNullable(this string value, DateTime defaultValue) + { + if (value.IsNullOrEmpty()) return defaultValue; + var dtvalues = value.Replace("-", "/").Replace(".", "/").ParseToInt32Collection("/").ToArray(); + DateTime dt = DateTime.MaxValue; + if (dtvalues.Count() == 3) + try + { + defaultValue = new DateTime(dtvalues[2], dtvalues[1], dtvalues[0]); + } + catch (Exception) { } + return defaultValue; + } + + public static DateTime? TryParseToDateDMYNullable(this string value) + { + DateTime? defaultValue = null; + if (value.IsNullOrEmpty()) return defaultValue; + var dtvalues = value.Replace("-", "/").Replace(".", "/").ParseToInt32Collection("/").ToArray(); + DateTime dt = DateTime.MaxValue; + if (dtvalues.Count() == 3) + try + { + defaultValue = new DateTime(dtvalues[2], dtvalues[1], dtvalues[0]); + } + catch (Exception) { } + return defaultValue; + } + + public static DateTime? TryParseToDateDMYHMSNullable(this string value) + { + DateTime? defaultValue = null; + if (value.IsNullOrEmpty()) return defaultValue; + var dtvalues = value.TrimStart().TrimEnd().Replace("-", "/").Replace(".", "/").Replace(" ", "/").Replace(":", "/").ParseToInt32Collection("/").ToArray(); + DateTime dt = DateTime.MaxValue; + if (dtvalues.Count() == 6) + try + { + defaultValue = new DateTime(dtvalues[2], dtvalues[1], dtvalues[0], dtvalues[3], dtvalues[4], dtvalues[5]); + } + catch (Exception ex) + { + var z = ex; + } + return defaultValue; + } + + public static DateTime TryParseToDateDMYFromExcel(this string value, DateTime defaultValue) + { + + return value.TryParseToDateDMYFromExcelNullable() ?? defaultValue; + } + public static DateTime? TryParseToDateDMYFromExcelNullable(this string value) + { + DateTime? defaultValue = null; + var days = value.TryParseToInt32(); + if (days != 0) + { + return new DateTime(1899, 12, 30).AddDays(days); + } + + if (value.IsNullOrEmpty()) return defaultValue; + var dtvalues = value.Replace("-", "/").Replace(".", "/").ParseToInt32Collection("/").ToArray(); + DateTime dt = DateTime.MaxValue; + if (dtvalues.Count() == 3) + try + { + defaultValue = new DateTime(dtvalues[2], dtvalues[1], dtvalues[0]); + } + catch (Exception) { } + + return defaultValue; + } + + public static Encoding TryParseToEncoding(this string value, Encoding defaultValue) + { + Encoding retVal = defaultValue; + try + { + retVal = Encoding.GetEncoding(value); + } + catch (Exception) { } // swallow any exceptions + + return retVal; + } + + #endregion + + public static bool ToLowerEquals(this string? one, string? other, bool nullEquals) + { + if (one != null && other != null) return one.ToLower() == other.ToLower(); + if (one == null && other == null) return nullEquals; + return false; + } + + public static bool ToLowerEquals(this string? one, string? other) + { + return one.ToLowerEquals(other, false); + } + + public static bool ToLowerContains(this string? one, string? other, bool nullEquals) + { + if (one != null && other != null) return one.ToLower().Contains(other.ToLower()); + if (one == null && other == null) return nullEquals; + return false; + } + public static bool ToLowerContains(this string? one, string? other) + { + return one.ToLowerContains(other, false); + } + public static bool ToLowerStartsWith(this string? one, string? other, bool nullEquals) + { + if (one != null && other != null) return one.ToLower().StartsWith(other.ToLower()); + if (one == null && other == null) return nullEquals; + return false; + } + public static bool ToLowerStartsWith(this string? one, string? other) + { + return one.ToLowerStartsWith(other, false); + } + public static bool TryParseToBool(this string? input, bool defaultValue) + { + bool result; + if (!bool.TryParse(input, out result)) + return defaultValue; + return result; + } + public static bool TryParseToBool(this string? input, string trueValue, bool defaultValue) + { + bool result; + if (!bool.TryParse(input.ToLowerContains(trueValue) ? "true" : "false", out result)) + return defaultValue; + return result; + } + public static bool TryParseToBool(this StringValues input, bool defaultValue) + { + bool result; + if (!bool.TryParse(input, out result)) + return defaultValue; + return result; + } + public static bool TryParseToBool(this StringValues input, string trueValue, bool defaultValue) + { + bool result; + if (!bool.TryParse(input.ToString().ToLowerContains(trueValue) ? "true" : "false", out result)) + return defaultValue; + return result; + } + public static bool IsExactlyEqualToBoolValue(this string input, bool theValueToCompareWith) + { + bool parsingOutput; + if (!bool.TryParse(input, out parsingOutput)) + return false; + return parsingOutput == theValueToCompareWith; + } + public static bool IsNotExactlyEqualToBoolValue(this string input, bool theValueToCompareWith) + { + bool parsingOutput; + if (!bool.TryParse(input, out parsingOutput)) + return true; + return parsingOutput != theValueToCompareWith; + } + + + #region MarkUp Helpers + public static string NotEmptyAttributeString(this string name, string value) + { + return value.IsNullOrEmpty() + ? String.Empty + : "{0}=\"{1}\"".ApplyFormat(name, value); + } + #endregion + + #region Generic Types + public static Type GetGenericType(this string type, params string[] typeArgs) + { + Type openType = Type.GetType(type, true); + Type[] types = typeArgs + .Select(tname => Type.GetType(tname)) + .Where(x => x != null) + .Select(x => x!) + .ToArray(); + return openType.MakeGenericType(types); + } + public static object GetGenericTypeInstance(this string type, params string[] typeArgs) + { + Type genericType = type.GetGenericType(typeArgs); + return Activator.CreateInstance(genericType); + } + public static object GetGenericTypeInstance(this string type, string[] typeArgs, object[] parameters) + { + Type genericType = type.GetGenericType(typeArgs); + return Activator.CreateInstance(genericType, parameters); + } + #endregion + + public static string MapPath(string path) + { + return Path.Combine( + (string)AppDomain.CurrentDomain.GetData("ContentRootPath"), + path); + } + + public static string ServerMapPath(this string virtualPath, HttpContext httpContext) + { + //HttpContext httpContext = HttpContext.Current; + + string rootPath = httpContext != null + ? MapPath("~/") + : Environment.CurrentDirectory; + + virtualPath = virtualPath.Remove("~/").Replace('/', '\\'); + + return Path.Combine(rootPath, virtualPath); + } + + public static string ServerMapPath(this string virtualPath) + { + if (virtualPath.StartsWith("~/") == false) + { + return virtualPath; + } + + string result = Environment.CurrentDirectory; + virtualPath = virtualPath.Remove("~/"); + string[] directories = virtualPath.Split('/'); + foreach (string directory in directories) + { + result = Path.Combine(result, directory); + } + return result; + } + + #region CSV File + public static DataTable ReadCSV(this string filePath, char separator = ';') + { + DataTable table = new DataTable(); + + using (var reader = File.OpenText(filePath)) + { + if (reader.EndOfStream == true) return table; + //columns + var columns = from columnName in reader.ReadLine().Split(separator) + select new DataColumn(columnName); + table.Columns.AddRange(columns.ToArray()); + //rows + while (reader.EndOfStream == false) + { + string line = reader.ReadLine(); + if (line.IsNullOrEmpty()) continue; + + var values = line.Split(separator); + var row = table.NewRow(); + for (int index = 0; index < table.Columns.Count; index++) row[index] = values[index]; + table.Rows.Add(row); + } + reader.Close(); + } + return table; + } + + public static void WriteCsv(this string filePath, DataTable table, char separator = ';', string[] columnNames = null) + { + if (columnNames == null) columnNames = table.Columns.Cast().Select(c => c.ColumnName).ToArray(); + StringBuilder content = new StringBuilder(); + content.AppendLine(columnNames.Join(separator.ToString())); + foreach (DataRow row in table.Rows) + { + bool firstColumn = true; + foreach (var columnName in columnNames) + { + if (firstColumn) { firstColumn = false; } else { content.Append(separator); } + content.Append(row[columnName]); + } + content.AppendLine(); + } + File.WriteAllText(filePath, content.ToString(), Encoding.UTF8); + } + #endregion + + public static string Clear(this string sourceString) + { + #region chars map + + var map = new Dictionary(33); + map.Add('а', "a"); + map.Add('б', "b"); + map.Add('в', "v"); + map.Add('г', "g"); + map.Add('д', "d"); + map.Add('е', "e"); + map.Add('ё', "e"); + map.Add('ж', "zh"); + map.Add('з', "z"); + map.Add('и', "i"); + map.Add('й', "y"); + map.Add('к', "k"); + map.Add('л', "l"); + map.Add('м', "m"); + map.Add('н', "n"); + map.Add('о', "o"); + map.Add('п', "p"); + map.Add('р', "r"); + map.Add('с', "s"); + map.Add('т', "t"); + map.Add('у', "u"); + map.Add('ф', "f"); + map.Add('х', "h"); + map.Add('ц', "c"); + map.Add('ч', "ch"); + map.Add('ш', "sh"); + map.Add('щ', "sh"); + map.Add('ь', ""); + map.Add('ы', "i"); + map.Add('ъ', ""); + map.Add('э', "e"); + map.Add('ю', "u"); + map.Add('я', "ya"); + + map.Add('А', "A"); + map.Add('Б', "B"); + map.Add('В', "V"); + map.Add('Г', "G"); + map.Add('Д', "D"); + map.Add('Е', "E"); + map.Add('Ё', "E"); + map.Add('Ж', "Zh"); + map.Add('З', "Z"); + map.Add('И', "I"); + map.Add('Й', "Y"); + map.Add('К', "K"); + map.Add('Л', "L"); + map.Add('М', "M"); + map.Add('Н', "N"); + map.Add('О', "O"); + map.Add('П', "P"); + map.Add('Р', "R"); + map.Add('С', "S"); + map.Add('Т', "T"); + map.Add('У', "U"); + map.Add('Ф', "F"); + map.Add('Х', "H"); + map.Add('Ц', "C"); + map.Add('Ч', "Ch"); + map.Add('Ш', "Sh"); + map.Add('Щ', "Sh"); + map.Add('Ь', ""); + map.Add('Ы', "I"); + map.Add('Ъ', ""); + map.Add('Э', "E"); + map.Add('Ю', "U"); + map.Add('Я', "Ya"); + + map.Add('a', "a"); + map.Add('b', "b"); + map.Add('c', "c"); + map.Add('d', "d"); + map.Add('e', "e"); + map.Add('f', "f"); + map.Add('g', "g"); + map.Add('h', "h"); + map.Add('i', "i"); + map.Add('j', "j"); + map.Add('k', "k"); + map.Add('l', "l"); + map.Add('m', "m"); + map.Add('n', "n"); + map.Add('o', "o"); + map.Add('p', "p"); + map.Add('q', "q"); + map.Add('r', "r"); + map.Add('s', "s"); + map.Add('t', "t"); + map.Add('u', "u"); + map.Add('v', "v"); + map.Add('w', "w"); + map.Add('x', "x"); + map.Add('y', "y"); + map.Add('z', "z"); + + map.Add('A', "A"); + map.Add('B', "B"); + map.Add('C', "C"); + map.Add('D', "D"); + map.Add('E', "E"); + map.Add('F', "F"); + map.Add('G', "G"); + map.Add('H', "H"); + map.Add('I', "I"); + map.Add('J', "J"); + map.Add('K', "K"); + map.Add('L', "L"); + map.Add('M', "M"); + map.Add('N', "N"); + map.Add('O', "O"); + map.Add('P', "P"); + map.Add('Q', "Q"); + map.Add('R', "R"); + map.Add('S', "S"); + map.Add('T', "T"); + map.Add('U', "U"); + map.Add('V', "V"); + map.Add('W', "W"); + map.Add('X', "X"); + map.Add('Y', "Y"); + + map.Add('1', "1"); + map.Add('2', "2"); + map.Add('3', "3"); + map.Add('4', "4"); + map.Add('5', "5"); + map.Add('6', "6"); + map.Add('7', "7"); + map.Add('8', "8"); + map.Add('9', "9"); + map.Add('0', "0"); + + map.Add('-', "-"); + map.Add(' ', " "); + map.Add('.', "."); + map.Add('_', "_"); + + #endregion + + var sb = new StringBuilder(sourceString.Count()); + sourceString.Where(x => map.ContainsKey(x)).ForEach(x => sb.Append(x)); + + return sb.ToString(); + } + + public static string TranslitToEnglish( + this string russian, + int maxLength = 512, + bool replaceOthers = true, + char otherSubstitute = '-', + char spaceSubstitute = '-', + bool toLower = true, + bool deleteDoubleSpace = true) + { + + #region chars map + + var map = new Dictionary(33); + map.Add('а', "a"); + map.Add('б', "b"); + map.Add('в', "v"); + map.Add('г', "g"); + map.Add('д', "d"); + map.Add('е', "e"); + map.Add('ё', "e"); + map.Add('ж', "zh"); + map.Add('з', "z"); + map.Add('и', "i"); + map.Add('й', "y"); + map.Add('к', "k"); + map.Add('л', "l"); + map.Add('м', "m"); + map.Add('н', "n"); + map.Add('о', "o"); + map.Add('п', "p"); + map.Add('р', "r"); + map.Add('с', "s"); + map.Add('т', "t"); + map.Add('у', "u"); + map.Add('ф', "f"); + map.Add('х', "h"); + map.Add('ц', "c"); + map.Add('ч', "ch"); + map.Add('ш', "sh"); + map.Add('щ', "sh"); + map.Add('ь', ""); + map.Add('ы', "i"); + map.Add('ъ', ""); + map.Add('э', "e"); + map.Add('ю', "u"); + map.Add('я', "ya"); + + map.Add('А', "A"); + map.Add('Б', "B"); + map.Add('В', "V"); + map.Add('Г', "G"); + map.Add('Д', "D"); + map.Add('Е', "E"); + map.Add('Ё', "E"); + map.Add('Ж', "Zh"); + map.Add('З', "Z"); + map.Add('И', "I"); + map.Add('Й', "Y"); + map.Add('К', "K"); + map.Add('Л', "L"); + map.Add('М', "M"); + map.Add('Н', "N"); + map.Add('О', "O"); + map.Add('П', "P"); + map.Add('Р', "R"); + map.Add('С', "S"); + map.Add('Т', "T"); + map.Add('У', "U"); + map.Add('Ф', "F"); + map.Add('Х', "H"); + map.Add('Ц', "C"); + map.Add('Ч', "Ch"); + map.Add('Ш', "Sh"); + map.Add('Щ', "Sh"); + map.Add('Ь', ""); + map.Add('Ы', "I"); + map.Add('Ъ', ""); + map.Add('Э', "E"); + map.Add('Ю', "U"); + map.Add('Я', "Ya"); + + map.Add('a', "a"); + map.Add('b', "b"); + map.Add('c', "c"); + map.Add('d', "d"); + map.Add('e', "e"); + map.Add('f', "f"); + map.Add('g', "g"); + map.Add('h', "h"); + map.Add('i', "i"); + map.Add('j', "j"); + map.Add('k', "k"); + map.Add('l', "l"); + map.Add('m', "m"); + map.Add('n', "n"); + map.Add('o', "o"); + map.Add('p', "p"); + map.Add('q', "q"); + map.Add('r', "r"); + map.Add('s', "s"); + map.Add('t', "t"); + map.Add('u', "u"); + map.Add('v', "v"); + map.Add('w', "w"); + map.Add('x', "x"); + map.Add('y', "y"); + map.Add('z', "z"); + + map.Add('A', "A"); + map.Add('B', "B"); + map.Add('C', "C"); + map.Add('D', "D"); + map.Add('E', "E"); + map.Add('F', "F"); + map.Add('G', "G"); + map.Add('H', "H"); + map.Add('I', "I"); + map.Add('J', "J"); + map.Add('K', "K"); + map.Add('L', "L"); + map.Add('M', "M"); + map.Add('N', "N"); + map.Add('O', "O"); + map.Add('P', "P"); + map.Add('Q', "Q"); + map.Add('R', "R"); + map.Add('S', "S"); + map.Add('T', "T"); + map.Add('U', "U"); + map.Add('V', "V"); + map.Add('W', "W"); + map.Add('X', "X"); + map.Add('Y', "Y"); + + map.Add('1', "1"); + map.Add('2', "2"); + map.Add('3', "3"); + map.Add('4', "4"); + map.Add('5', "5"); + map.Add('6', "6"); + map.Add('7', "7"); + map.Add('8', "8"); + map.Add('9', "9"); + map.Add('0', "0"); + + #endregion + + var result = new StringBuilder(); + int i = 0; + foreach (char c in russian) + { + ++i; + if (c == ' ') + { + if (i >= maxLength) + { + break; + } + result.Append(spaceSubstitute); + } + else if (map.ContainsKey(c)) result.Append(map[c]); + else if (!replaceOthers) result.Append(c); + else result.Append(otherSubstitute); + } + string resultKey = result.ToString() + .Trim(spaceSubstitute); + + if (toLower) resultKey = resultKey.ToLower(); + if (deleteDoubleSpace) + { + string doubleSpace = "{0}{0}".ApplyFormat(spaceSubstitute); + while (resultKey.Contains(doubleSpace)) resultKey = resultKey.Replace(doubleSpace, spaceSubstitute.ToString()); + } + return resultKey; + } + + + private static readonly object LockLog = new object(); + public static void AppendTextToFile(this string path, string format, HttpContext httpContext, params object[] parameters) + { + lock (LockLog) + { + var filePath = path.ServerMapPath(httpContext); + + // if (!File.Exists(filePath)) File.Create(filePath); + + using (var writer = System.IO.File.AppendText(filePath)) + { + writer.WriteLine(format, parameters); + } + } + } + + public static string Convert(this string source, System.Text.Encoding encoding) + { + if (string.IsNullOrEmpty(source)) return string.Empty; + Encoding utf8 = Encoding.Default; + byte[] utf8Bytes = encoding.GetBytes(source); + byte[] win1251Bytes = Encoding.Convert(utf8, encoding, utf8Bytes); + + return encoding.GetString(win1251Bytes); + } + + public static string ConvertToBase64(this string source) + { + if (string.IsNullOrEmpty(source)) return string.Empty; + return System.Convert.ToBase64String(new UTF8Encoding(false).GetBytes(source)); + } + + public static string ConvertFromBase64(this string source) + { + if (string.IsNullOrEmpty(source)) return string.Empty; + return new UTF8Encoding(false).GetString(System.Convert.FromBase64String(source)); + } + + public static bool CheckBase64Format(this string source) + { + if (string.IsNullOrEmpty(source)) return true; + + try + { + System.Convert.FromBase64CharArray(source.ToCharArray(), 0, source.Length); + } + catch + { + return false; + } + + return true; + } + + //public static string ContentBlockReplace(this string content) + //{ + // return content; + // //return content.Replace("", "") + // // .Replace("", "") + // // .Replace("", "
") + // // .Replace("", "
"); + //} + + + private static readonly Dictionary MIMETypesDictionary = new Dictionary + { + {"ai", "application/postscript"}, + {"aif", "audio/x-aiff"}, + {"aifc", "audio/x-aiff"}, + {"aiff", "audio/x-aiff"}, + {"asc", "text/plain"}, + {"atom", "application/atom+xml"}, + {"au", "audio/basic"}, + {"avi", "video/x-msvideo"}, + {"bcpio", "application/x-bcpio"}, + {"bin", "application/octet-stream"}, + {"bmp", "image/bmp"}, + {"cdf", "application/x-netcdf"}, + {"cgm", "image/cgm"}, + {"class", "application/octet-stream"}, + {"cpio", "application/x-cpio"}, + {"cpt", "application/mac-compactpro"}, + {"csh", "application/x-csh"}, + {"css", "text/css"}, + {"dcr", "application/x-director"}, + {"dif", "video/x-dv"}, + {"dir", "application/x-director"}, + {"djv", "image/vnd.djvu"}, + {"djvu", "image/vnd.djvu"}, + {"dll", "application/octet-stream"}, + {"dmg", "application/octet-stream"}, + {"dms", "application/octet-stream"}, + {"doc", "application/msword"}, + {"docx","application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, + {"dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"}, + {"docm","application/vnd.ms-word.document.macroEnabled.12"}, + {"dotm","application/vnd.ms-word.template.macroEnabled.12"}, + {"dtd", "application/xml-dtd"}, + {"dv", "video/x-dv"}, + {"dvi", "application/x-dvi"}, + {"dxr", "application/x-director"}, + {"eps", "application/postscript"}, + {"etx", "text/x-setext"}, + {"exe", "application/octet-stream"}, + {"ez", "application/andrew-inset"}, + {"gif", "image/gif"}, + {"gram", "application/srgs"}, + {"grxml", "application/srgs+xml"}, + {"gtar", "application/x-gtar"}, + {"hdf", "application/x-hdf"}, + {"hqx", "application/mac-binhex40"}, + {"htm", "text/html"}, + {"html", "text/html"}, + {"ice", "x-conference/x-cooltalk"}, + {"ico", "image/x-icon"}, + {"ics", "text/calendar"}, + {"ief", "image/ief"}, + {"ifb", "text/calendar"}, + {"iges", "model/iges"}, + {"igs", "model/iges"}, + {"jnlp", "application/x-java-jnlp-file"}, + {"jp2", "image/jp2"}, + {"jpe", "image/jpeg"}, + {"jpeg", "image/jpeg"}, + {"jpg", "image/jpeg"}, + {"js", "application/x-javascript"}, + {"kar", "audio/midi"}, + {"latex", "application/x-latex"}, + {"lha", "application/octet-stream"}, + {"lzh", "application/octet-stream"}, + {"m3u", "audio/x-mpegurl"}, + {"m4a", "audio/mp4a-latm"}, + {"m4b", "audio/mp4a-latm"}, + {"m4p", "audio/mp4a-latm"}, + {"m4u", "video/vnd.mpegurl"}, + {"m4v", "video/x-m4v"}, + {"mac", "image/x-macpaint"}, + {"man", "application/x-troff-man"}, + {"mathml", "application/mathml+xml"}, + {"me", "application/x-troff-me"}, + {"mesh", "model/mesh"}, + {"mid", "audio/midi"}, + {"midi", "audio/midi"}, + {"mif", "application/vnd.mif"}, + {"mov", "video/quicktime"}, + {"movie", "video/x-sgi-movie"}, + {"mp2", "audio/mpeg"}, + {"mp3", "audio/mpeg"}, + {"mp4", "video/mp4"}, + {"mpe", "video/mpeg"}, + {"mpeg", "video/mpeg"}, + {"mpg", "video/mpeg"}, + {"mpga", "audio/mpeg"}, + {"ms", "application/x-troff-ms"}, + {"msh", "model/mesh"}, + {"mxu", "video/vnd.mpegurl"}, + {"nc", "application/x-netcdf"}, + {"oda", "application/oda"}, + {"ogg", "application/ogg"}, + {"pbm", "image/x-portable-bitmap"}, + {"pct", "image/pict"}, + {"pdb", "chemical/x-pdb"}, + {"pdf", "application/pdf"}, + {"pgm", "image/x-portable-graymap"}, + {"pgn", "application/x-chess-pgn"}, + {"pic", "image/pict"}, + {"pict", "image/pict"}, + {"png", "image/png"}, + {"pnm", "image/x-portable-anymap"}, + {"pnt", "image/x-macpaint"}, + {"pntg", "image/x-macpaint"}, + {"ppm", "image/x-portable-pixmap"}, + {"ppt", "application/vnd.ms-powerpoint"}, + {"pptx","application/vnd.openxmlformats-officedocument.presentationml.presentation"}, + {"potx","application/vnd.openxmlformats-officedocument.presentationml.template"}, + {"ppsx","application/vnd.openxmlformats-officedocument.presentationml.slideshow"}, + {"ppam","application/vnd.ms-powerpoint.addin.macroEnabled.12"}, + {"pptm","application/vnd.ms-powerpoint.presentation.macroEnabled.12"}, + {"potm","application/vnd.ms-powerpoint.template.macroEnabled.12"}, + {"ppsm","application/vnd.ms-powerpoint.slideshow.macroEnabled.12"}, + {"ps", "application/postscript"}, + {"qt", "video/quicktime"}, + {"qti", "image/x-quicktime"}, + {"qtif", "image/x-quicktime"}, + {"ra", "audio/x-pn-realaudio"}, + {"ram", "audio/x-pn-realaudio"}, + {"ras", "image/x-cmu-raster"}, + {"rdf", "application/rdf+xml"}, + {"rgb", "image/x-rgb"}, + {"rm", "application/vnd.rn-realmedia"}, + {"roff", "application/x-troff"}, + {"rtf", "text/rtf"}, + {"rtx", "text/richtext"}, + {"sgm", "text/sgml"}, + {"sgml", "text/sgml"}, + {"sh", "application/x-sh"}, + {"shar", "application/x-shar"}, + {"silo", "model/mesh"}, + {"sit", "application/x-stuffit"}, + {"skd", "application/x-koan"}, + {"skm", "application/x-koan"}, + {"skp", "application/x-koan"}, + {"skt", "application/x-koan"}, + {"smi", "application/smil"}, + {"smil", "application/smil"}, + {"snd", "audio/basic"}, + {"so", "application/octet-stream"}, + {"spl", "application/x-futuresplash"}, + {"src", "application/x-wais-source"}, + {"sv4cpio", "application/x-sv4cpio"}, + {"sv4crc", "application/x-sv4crc"}, + {"svg", "image/svg+xml"}, + {"swf", "application/x-shockwave-flash"}, + {"t", "application/x-troff"}, + {"tar", "application/x-tar"}, + {"tcl", "application/x-tcl"}, + {"tex", "application/x-tex"}, + {"texi", "application/x-texinfo"}, + {"texinfo", "application/x-texinfo"}, + {"tif", "image/tiff"}, + {"tiff", "image/tiff"}, + {"tr", "application/x-troff"}, + {"tsv", "text/tab-separated-values"}, + {"txt", "text/plain"}, + {"ustar", "application/x-ustar"}, + {"vcd", "application/x-cdlink"}, + {"vrml", "model/vrml"}, + {"vxml", "application/voicexml+xml"}, + {"wav", "audio/x-wav"}, + {"wbmp", "image/vnd.wap.wbmp"}, + {"wbmxl", "application/vnd.wap.wbxml"}, + {"wml", "text/vnd.wap.wml"}, + {"wmlc", "application/vnd.wap.wmlc"}, + {"wmls", "text/vnd.wap.wmlscript"}, + {"wmlsc", "application/vnd.wap.wmlscriptc"}, + {"wrl", "model/vrml"}, + {"xbm", "image/x-xbitmap"}, + {"xht", "application/xhtml+xml"}, + {"xhtml", "application/xhtml+xml"}, + {"xls", "application/vnd.ms-excel"}, + {"xml", "application/xml"}, + {"xpm", "image/x-xpixmap"}, + {"xsl", "application/xml"}, + {"xlsx","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, + {"xltx","application/vnd.openxmlformats-officedocument.spreadsheetml.template"}, + {"xlsm","application/vnd.ms-excel.sheet.macroEnabled.12"}, + {"xltm","application/vnd.ms-excel.template.macroEnabled.12"}, + {"xlam","application/vnd.ms-excel.addin.macroEnabled.12"}, + {"xlsb","application/vnd.ms-excel.sheet.binary.macroEnabled.12"}, + {"xslt", "application/xslt+xml"}, + {"xul", "application/vnd.mozilla.xul+xml"}, + {"xwd", "image/x-xwindowdump"}, + {"xyz", "chemical/x-xyz"}, + {"zip", "application/zip"} + }; + + public static string GetMimeType(this string fileName) + { + //get file extension + string extension = Path.GetExtension(fileName).ToLowerInvariant(); + + if (extension.Length > 0 && + MIMETypesDictionary.ContainsKey(extension.Remove(0, 1))) + { + return MIMETypesDictionary[extension.Remove(0, 1)]; + } + return "unknown/unknown"; + } + + public static string ExistsOrCreateDirectory(this string path, HttpContext httpContext) + { + if (path.StartsWith("~")) path = path.ServerMapPath(httpContext); + if (!Directory.Exists(path)) Directory.CreateDirectory(path); + return path; + } + + public static string AddPrifixIsNotEmpty(this string input, string prefix) + { + if (!input.IsNullOrEmpty()) return "{0}{1}".ApplyFormat(prefix, input); + return string.Empty; + } + + public static string AddPrifixIsNotEmpty(this string input, string prefix, int count) + { + if (input.IsNullOrEmpty()) return string.Empty; + + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < count; i++) builder.Append(prefix); + return "{0}{1}".ApplyFormat(builder.ToString(), input); + } + public static string AddSufixIsNotEmpty(this string input, string sufix) + { + if (!input.IsNullOrEmpty()) return "{1}{0}".ApplyFormat(sufix, input); + return string.Empty; + } + + public static byte TryParseToByte(this string input, byte defaultValue) + { + byte result; + if (byte.TryParse(input, out result)) return result; + return defaultValue; + } + + public static Decimal? TryParseToDecimalNulable(this string input) + { + Decimal decimalValue = 0; + if (!Decimal.TryParse(input, out decimalValue)) return null; + return decimalValue; + } + + public static Double? TryParseToDoubleNulable(this string input) + { + Double decimalValue = 0; + if (!Double.TryParse(input, out decimalValue)) return null; + return decimalValue; + } + + public static decimal GetValueDecimal(this string input) + { + decimal value; + if (decimal.TryParse(input, NumberStyles.Any, ci.NumberFormat, out value)) + return value; + return decimal.MinValue; + } + + public static Guid TryParseToGuid(this string value, Guid defaultValue) + { + Guid td; + if (Guid.TryParse(value, out td)) return td; + return defaultValue; + } + + public static Guid? TryParseToGuidNulable(this string value, Guid? defaultValue) + { + Guid td; + if (Guid.TryParse(value, out td)) return td; + return defaultValue; + } + + public static DateTime TryParseToDateXMLNullable(this string value, DateTime defaultValue) + { + if (value.IsNullOrEmpty()) return defaultValue; + var collections = value.ParseToSplitCollection('T'); + if (collections.Count() == 1) + { + collections = collections.AddToNewList("00:00:00"); + } + var dtvalues = collections.FirstOrDefault().ParseToInt32Collection("-").ToArray(); + var timevalues = collections.LastOrDefault().ParseToInt32Collection(":").ToArray(); + DateTime dt = DateTime.MaxValue; + if (dtvalues.Count() == 3) + { + try + { + defaultValue = new DateTime(dtvalues[0], dtvalues[1], dtvalues[2], timevalues[0], timevalues[1], timevalues[2]); + } + catch (Exception) { } + } + return defaultValue; + } + + public static DateTime? TryParseToDateXMLNullable(this string value) + { + var result = value.TryParseToDateXMLNullable(DateTime.MinValue); + if (result == DateTime.MinValue) return null; + return result; + } + + public static string GetShortFIO(this string value) + { + string result = ""; + if (value == null || value.Trim() == "") + return result; + string[] arrayString = value.Split(' '); + for (int i = 0; i < arrayString.Length; i++) + { + if (i == 0) + { + result = arrayString[i]; + } + else + { + result += " " + arrayString[i].ToUpper().FirstOrDefault() + "."; + } + } + return result; + } + + public static string GetDigit(this string value) + { + return string.Join("", value.Where(c => char.IsDigit(c))); ; + } + + public static string GZipCompress(this string s, CompressionMode mode, Encoding encoding) + { + if (mode == CompressionMode.Compress) + { + using (var outputStream = new MemoryStream()) + { + using (var compressionStream = new GZipStream(outputStream, CompressionMode.Compress)) + using (var inputStream = new MemoryStream(encoding.GetBytes(s))) + inputStream.CopyTo(compressionStream); + + return System.Convert.ToBase64String(outputStream.ToArray()); + } + } + else + { + using (var outputStream = new MemoryStream()) + { + using (var inputStream = new MemoryStream(System.Convert.FromBase64String(s))) + using (var compressionStream = new GZipStream(inputStream, mode)) + compressionStream.CopyTo(outputStream); + + return encoding.GetString(outputStream.ToArray()); + } + } + } + + public static string ReadFileTextToEnd(this string fileName) + { + var reader = new StreamReader(fileName); + return reader.ReadToEnd(); + } + + + + public static string GetMD5Hash(this string value) + { + if (string.IsNullOrWhiteSpace(value)) return string.Empty; + using (MD5 md5 = MD5.Create()) + { + return md5.ComputeHash(Encoding.UTF8.GetBytes(value)).GetHashString(); + } + } + + public static string ToKeyHashMD5(this string value, int size = 256) + { + string hash = value.GetMD5Hash(); + string trans = value.TranslitToEnglish(); + string result = trans; + if (trans.Length > size) + { + result = trans.Substring(0, (size - hash.Length - 1)) + "_" + hash; + } + + return result; + } + + + public static string ToKeyHash(this string value, int size = 256) + { + string hash = value.GetHashString(); + string trans = value.TranslitToEnglish(); + string result = trans; + if (trans.Length > size) + { + result = trans.Substring(0, (size - hash.Length - 1)) + "_" + hash; + } + + return result; + } + + + public static byte[] GetHash(this string inputString) + { + using (HashAlgorithm algorithm = SHA256.Create()) + return algorithm.ComputeHash(Encoding.UTF8.GetBytes(inputString)); + } + + public static string GetHashString(this string inputString) => GetHash(inputString).GetHashString(); + + public static string GetHashString(this byte[] bytes) + { + StringBuilder sb = new StringBuilder(); + foreach (byte b in bytes) + sb.Append(b.ToString("X2")); + + return sb.ToString(); + } + + public static string RemoveFilenameForbidden( + this string inputString, + bool toLower = false, + bool deleteDoubleSpace = true, + bool deleteLastSpace = true) + { + #region Allowed chars + + string allowedSymbols = string.Empty; + allowedSymbols += "abcdefghijklmnopqrstuvwxyz"; + allowedSymbols += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + allowedSymbols += "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"; + allowedSymbols += "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ"; + + allowedSymbols += "0123456789"; + + allowedSymbols += "~.#№()-_ "; + + #endregion + + var result = new StringBuilder(); + + foreach (char c in inputString) + { + if (allowedSymbols.Contains(c)) + result.Append(c); + else + result.Append(' '); + } + string resultKey = result.ToString(); + + if (toLower) resultKey = resultKey.ToLower(); + if (deleteDoubleSpace) + { + while (resultKey.Contains(" ")) resultKey = resultKey.Replace(" ", " "); + } + if (deleteLastSpace) + { + while (resultKey.Last() == ' ') resultKey = resultKey.Substring(0, resultKey.Length - 1); + } + return resultKey; + } + public static string RemovePathForbidden( + this string inputString, + bool toLower = false, + bool deleteDoubleSpace = true, + bool deleteLastSpace = true) + { + #region Allowed chars + + string allowedSymbols = string.Empty; + allowedSymbols += "abcdefghijklmnopqrstuvwxyz"; + allowedSymbols += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + allowedSymbols += "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"; + allowedSymbols += "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ"; + + allowedSymbols += "0123456789"; + + allowedSymbols += "/~.#№()-_ "; + + #endregion + + var result = new StringBuilder(); + + foreach (char c in inputString) + { + if (allowedSymbols.Contains(c)) + result.Append(c); + else + result.Append(' '); + } + string resultKey = result.ToString(); + + if (toLower) resultKey = resultKey.ToLower(); + if (deleteDoubleSpace) + { + while (resultKey.Contains(" ")) resultKey = resultKey.Replace(" ", " "); + } + if (deleteLastSpace) + { + while (resultKey.Last() == ' ') resultKey = resultKey.Substring(0, resultKey.Length - 1); + } + return resultKey; + } + + public static string CreateUserToken(this Guid guid) + { + return guid.ToString().Replace("-", string.Empty); + } + + public static string AppendToken(this string str, int tokenLength = 16) + { + return str + str.GenerateToken(tokenLength); + } + + private static readonly Random _random = new Random(); + + private const string _alphabetLower = "abcdefghijklmnopqrstuvwxyz"; + private const string _alphabetUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + private const string _digitDec = "0123456789"; + private const string _digitHex = "abcdef"; + + public static string GenerateToken(this string str, int tokenLength, GenerateTokenAlphabetMode alphabetMode = GenerateTokenAlphabetMode.AnyCase, GenerateTokenDigitMode digitMode = GenerateTokenDigitMode.Dec) + { + string allowedChars = string.Empty; + + switch (alphabetMode) + { + case GenerateTokenAlphabetMode.None: + break; + + case GenerateTokenAlphabetMode.AnyCase: + allowedChars += _alphabetLower + _alphabetUpper; + break; + + case GenerateTokenAlphabetMode.LowerCase: + allowedChars += _alphabetLower; + break; + + case GenerateTokenAlphabetMode.UpperCase: + allowedChars += _alphabetUpper; + break; + } + + switch (digitMode) + { + case GenerateTokenDigitMode.None: + break; + + case GenerateTokenDigitMode.Hex: + allowedChars += _digitDec + _digitHex; + break; + + case GenerateTokenDigitMode.Dec: + allowedChars += _digitDec; + break; + } + + if (allowedChars.Length < 1) return string.Empty; + + StringBuilder sb = new StringBuilder(tokenLength); + + for (int i = 0; i < tokenLength; i++) + { + int charIndex = _random.Next() % allowedChars.Length; + sb.Append(allowedChars[charIndex]); + } + + return sb.ToString(); + } + + public static string ToLinuxPath(this string path) + { + return path.Replace("\\", "/"); + } + + public static string TrimPath(this string path) + { + return path.TrimStart('/', '\\'); + } + + public static string ReadEmbeddedResource(this string resourceName) + { + // Получаем путь к сборке + var assembly = System.Reflection.Assembly.GetExecutingAssembly(); + + // Читаем ресурс + using (Stream? stream = assembly.GetManifestResourceStream(resourceName)) + { + if (stream == null) throw new FileNotFoundException($"Resource '{resourceName}' not found"); + using (StreamReader reader = new StreamReader(stream)) + { + return reader.ReadToEnd(); + } + } + } + + public static string CreateSQLiteConnectionString(this string filePath, string mode = "ReadWrite") + { + return $"Data Source={filePath};Mode={mode};"; + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Extension/System.Web.Mvc.cs b/LibCommon/Kit.Core.Helpers/Extension/System.Web.Mvc.cs new file mode 100644 index 0000000..ec59f59 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Extension/System.Web.Mvc.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace Kit.Helpers +{ + public static class SystemWebMvcExtensions + { + /// + /// Преобразует перечень объектов в перечень элементов html-списка + /// + /// Тип преобразуемого объекта + /// Перечень преобразуемых объектов + /// Функция, возвращающая значение, используемое в качестве Value для элемента списка. + /// Для преобразования возвращаемого значения в строку вызывается стандартная реализация ToString() + /// Функция, возвращающая значение, используемое в качестве Text для элемента списка. + /// Для преобразования возвращаемого значения в строку вызывается стандартная реализация ToString() + public static IEnumerable ToSelectList( + this IEnumerable items, + Func valueSelector, + Func textSelector) + { + if (valueSelector == null) throw new ArgumentNullException("valueSelector"); + if (textSelector == null) throw new ArgumentNullException("textSelector"); + + return items.Select(i => new SelectListItem + { + Value = valueSelector(i).ToString(), + Text = textSelector(i).ToString() + }); + } + + /// + /// Преобразует перечень объектов в перечень элементов html-списка + /// + /// Тип преобразуемого объекта + /// Перечень преобразуемых объектов + /// Функция, возвращающая значение, используемое в качестве Value для элемента списка. + /// Функция, возвращающая значение, используемое в качестве Text для элемента списка. + public static IEnumerable ToSelectList( + this IEnumerable items, + Func valueSelector, + Func textSelector) + { + if (valueSelector == null) throw new ArgumentNullException("valueSelector"); + if (textSelector == null) throw new ArgumentNullException("textSelector"); + + return items.Select(i => new SelectListItem + { + Value = valueSelector(i), + Text = textSelector(i) + }); + } + + /// + /// Преобразует перечень объектов в перечень элементов html-списка + /// + /// Тип преобразуемого объекта + /// Перечень преобразуемых объектов + /// Функция, возвращающая значение, используемое в качестве Value для элемента списка. + /// Функция, возвращающая значение, используемое в качестве Text для элемента списка. + /// Список значений, которые необходимо отметить в результирующем списке как выбранные. + public static IEnumerable ToSelectList( + this IEnumerable items, + Func valueSelector, + Func textSelector, + Func disabled) + { + if (valueSelector == null) throw new ArgumentNullException("valueSelector"); + if (textSelector == null) throw new ArgumentNullException("textSelector"); + + return items.Select(i => new SelectListItem + { + Value = (valueSelector(i) != null ? valueSelector(i).ToString() : string.Empty), + Text = (textSelector(i) != null ? textSelector(i).ToString() : string.Empty), + Selected = disabled(i) + }); + } + + + /// + /// Преобразует перечень объектов в перечень элементов html-списка + /// + /// Тип преобразуемого объекта + /// Перечень преобразуемых объектов + /// Функция, возвращающая значение, используемое в качестве Value для элемента списка. + /// Функция, возвращающая значение, используемое в качестве Text для элемента списка. + /// Список значений, которые необходимо отметить в результирующем списке как выбранные. + public static IEnumerable ToSelectList( + this IEnumerable items, + Func valueSelector, + Func textSelector, + TValue selectedValue) + { + if (valueSelector == null) throw new ArgumentNullException("valueSelector"); + if (textSelector == null) throw new ArgumentNullException("textSelector"); + + return items.Select(i => new SelectListItem + { + Value = (valueSelector(i) != null ? valueSelector(i).ToString() : string.Empty), + Text = (textSelector(i) != null ? textSelector(i).ToString() : string.Empty), + Selected = (selectedValue != null && selectedValue.Equals(valueSelector(i))) + }); + } + + /// + /// Преобразует перечень объектов в перечень элементов html-списка + /// + /// Тип преобразуемого объекта + /// Перечень преобразуемых объектов + /// Функция, возвращающая значение, используемое в качестве Value для элемента списка. + /// Функция, возвращающая значение, используемое в качестве Text для элемента списка. + /// Список значений, которые необходимо отметить в результирующем списке как выбранные. + public static IEnumerable ToSelectList( + this IEnumerable items, + Func valueSelector, + Func textSelector, + IEnumerable selectedValues) + { + if (valueSelector == null) throw new ArgumentNullException("valueSelector"); + if (textSelector == null) throw new ArgumentNullException("textSelector"); + + return items.Select(i => new SelectListItem + { + Value = (valueSelector(i) != null ? valueSelector(i).ToString() : string.Empty), + Text = (textSelector(i) != null ? textSelector(i).ToString() : string.Empty), + Selected = selectedValues != null && selectedValues.Contains(valueSelector(i)) + }); + } + + + private static string GetLambdaAction(LambdaExpression actionSelector) + { + UnaryExpression? unaryExpression = (UnaryExpression)actionSelector.Body; + MethodCallExpression? methodCallExpression = (MethodCallExpression)unaryExpression.Operand; + ConstantExpression? constantExpression = (ConstantExpression)methodCallExpression.Object; + MethodInfo? methodInfo = (MethodInfo)constantExpression.Value; + string actionName = methodInfo.Name; + + return actionName; + } + + public static string? Action(this IUrlHelper urlHelper, Expression> actionSelector, object? values = null, string? protocol = null) where TController : Controller + { + // получаем имя контроллера + string controllerName = typeof(TController).Name; + if (controllerName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) && controllerName.Length > "Controller".Length) + { + controllerName = controllerName.Remove(controllerName.Length - "Controller".Length, "Controller".Length); + } + + return urlHelper.Action(GetLambdaAction(actionSelector), controllerName, values, protocol); + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Extension/System.Xml.XmlNode.cs b/LibCommon/Kit.Core.Helpers/Extension/System.Xml.XmlNode.cs new file mode 100644 index 0000000..e214ea6 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Extension/System.Xml.XmlNode.cs @@ -0,0 +1,744 @@ +using System.Globalization; + +namespace Kit.Helpers +{ + using System; + using System.Xml; + using System.Linq; + using System.Collections.Generic; + + public enum ItemLogTypes + { + None = 0, + Create = 1, + Update = 2, + Delete = 3, + } + + public class ItemLogGenerator + { + public object ObjectId { get; set; } + public string ObjectType { get; set; } + + public ItemLogGenerator(object objectId, string objectType) + { + ObjectId = objectId; + ObjectType = objectType; + } + + public List Items { get; set; } = new List(); + + public ItemLogGenerator Add(ItemLog item) + { + if (item != null) + { + Items.Add(new ItemLog(ObjectId, ObjectType, item.Name, item.Title) { Type = item.Type, ValueNew = item.ValueNew, ValueOld = item.ValueOld }); + } + + return this; + } + + public ItemLogGenerator AddRange(IEnumerable items) + { + if (items.IsNullOrEmpty() == false) + { + items.ForEach(item => + { + Items.Add(new ItemLog(ObjectId, ObjectType, item.Name, item.Title) { Type = item.Type, ValueNew = item.ValueNew, ValueOld = item.ValueOld }); + }); + } + + return this; + } + } + + public class ItemLog + { + public ItemLogTypes Type { get; set; } + + public ItemLog(string name, string? title = null) + { + Type = ItemLogTypes.None; + + Name = name ?? string.Empty; + Title = title ?? string.Empty; + } + + public ItemLog(object objectId, string objectType, string name, string? title = null) : this(name, title) + { + ObjectId = objectId; + ObjectType = objectType ?? string.Empty; + } + + public object ObjectId { get; set; } + public string ObjectType { get; set; } + public string Name { get; set; } + public string Title { get; set; } + public string ValueOld { get; set; } + public string ValueNew { get; set; } + + public ItemLog FillType() + { + bool hasValueOld = string.IsNullOrEmpty(ValueOld) == false; + bool hasValueNew = string.IsNullOrEmpty(ValueNew) == false; + + if (hasValueOld == false && hasValueNew == false) + { + Type = ItemLogTypes.None; + return this; + } + + if (hasValueOld == false && hasValueNew) + { + Type = ItemLogTypes.Create; + return this; + } + + if (hasValueOld && hasValueNew == false) + { + Type = ItemLogTypes.Delete; + return this; + } + + if (ValueOld.Equals(ValueNew) == false) + { + Type = ItemLogTypes.Update; + return this; + } + + Type = ItemLogTypes.None; + return this; + } + } + + public static class SystemXmlNodeExtensionMethods + { + private static readonly CultureInfo ci = new CultureInfo("en-US"); + + public static void RemoveAttribute(this XmlDocument input, string name) + { + foreach (XmlNode item in input.SelectNodes("//*[@{0}]".ApplyFormat(name))) + { + item.Attributes.Remove(item.Attributes[name]); + } + } + + public static XmlNode SetElementValue(this XmlNode input, string name, string value) + { + var element = input.SelectSingleNode(name); + if (element == null) + { + element = input.OwnerDocument.CreateElement(name); + input.AppendChild(element); + } + element.InnerText = value; + return element; + } + + public static XmlNode SetElementValue(this XmlNode input, string name, DateTime value) + { + var element = input.SelectSingleNode(name); + if (element == null) + { + element = input.OwnerDocument.CreateElement(name); + input.AppendChild(element); + } + element.InnerText = XmlConvert.ToString(value, XmlDateTimeSerializationMode.RoundtripKind); + return element; + } + + public static XmlNode SetElement(this XmlNode input, string name, Object value) + { + var element = input.SelectSingleNode(name); + if (element == null) + { + element = input.OwnerDocument.CreateElement(name); + input.AppendChild(element); + } + element.InnerText = value.ToString(); + return element; + } + + + public static XmlNode SetElementCDataValue(this XmlNode input, string name, string value) + { + var element = input.SelectSingleNode(name); + if (element == null) + { + var root = input.OwnerDocument.CreateElement(name); + element = input.OwnerDocument.CreateNode(XmlNodeType.CDATA, name, null); + root.AppendChild(element); + input.AppendChild(root); + } + element.InnerText = value; + return element; + } + + + public static XmlNode SetElementValue(this XmlNode input, string name, decimal value) + { + return input.SetElementValue(name, value.ToString(System.Globalization.CultureInfo.InvariantCulture)); + } + + public static XmlNode SetElementValue(this XmlNode input, string name, decimal? value, string elementsAfterPostion) + { + if (value.HasValue) + { + return input.SetElementNullableValue(name, elementsAfterPostion, value.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)); + } + else + { + return input.SetElementNullableValue(name, null); + } + } + + public static XmlNode SetElementValue(this XmlNode input, string name, int? value, string elementsAfterPostion) + { + if (value.HasValue && value != 0) + { + return input.SetElementNullableValue(name, elementsAfterPostion, value.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)); + } + else + { + return input.SetElementNullableValue(name, null); + } + } + + private static XmlNode GetElementAfterInsert(XmlNode root, string elementsAfterPostion) + { + if (!elementsAfterPostion.IsNullOrEmpty()) + { + var elements = root.SelectNodes(elementsAfterPostion); + return elements.Cast().LastOrDefault(); + } + return null; + } + + public static XmlNode SetElementNullableValue(this XmlNode input, string name, string value) + { + return input.SetElementNullableValue(name, "", value); + } + + public static XmlNode SetElementNullableValue(this XmlNode input, string name, string elementsAfterPostion, string value) + { + if (!value.IsNullOrEmpty()) + { + var element = input.SelectSingleNode(name); + if (element != null) + { + input.RemoveChild(element); + } + + element = input.OwnerDocument.CreateElement(name); + var after = GetElementAfterInsert(input, elementsAfterPostion); + if (after == null) + input.InsertBefore(element, input.FirstChild); + else + input.InsertAfter(element, after); + + element.InnerText = value; + + return element; + } + else + { + var child = input.SelectSingleNode(name); + if (child != null) + input.RemoveChild(child); + return null; + } + } + + public static XmlNode CreateElement(this XmlNode input, string name) => input.CreateElement(input.OwnerDocument, name); + + public static XmlNode CreateElement(this XmlNode input, XmlDocument xmlDocument, string name) + { + //var element = input.SelectSingleNode(name); + //if (element == null) + //{ + var element = xmlDocument.CreateElement(name); + input.AppendChild(element); + //} + return element; + } + + public static XmlAttribute CreateAttributeValue(this XmlNode input, string name, string value) + { + XmlAttribute element = null; + //try + { + element = input.Attributes[name]; + } + //catch + { + + if (element == null) + { + element = input.OwnerDocument.CreateAttribute(name); + input.Attributes.Append(element); + } + element.InnerText = value; + } + return element; + } + + public static bool HasAttribute(this XmlNode input, string name) + { + return input.Attributes[name] != null; + } + + public static void SetAttribute(this XmlNode input, string name, Object value) + { + XmlAttribute element = null; + //try + { + element = input.Attributes[name]; + } + //catch { } + if (element == null) + { + element = input.OwnerDocument.CreateAttribute(name); + input.Attributes.Append(element); + } + element.InnerText = value.ToString(); + } + + public static void SetAttributeNullable(this XmlNode input, string name, string? value) + { + if (string.IsNullOrWhiteSpace(value)) + { + input.RemoveAttribute(name); + } + else + { + input.SetAttribute(name, value); + } + } + + public static void RemoveAttribute(this XmlNode input, string name) + { + if (input.Attributes == null || input.Attributes.Count == 0) return; + + XmlAttribute? element = input.Attributes[name]; + if (element == null) return; + + ((XmlElement)input).RemoveAttribute(name); + } + + public static void SetAttributeValue(this XmlNode input, string name, string value) + { + XmlAttribute element = null; + //try + { + element = input.Attributes[name]; + } + //catch { } + if (element == null) + { + element = input.OwnerDocument.CreateAttribute(name); + input.Attributes.Append(element); + } + element.InnerText = value; + } + + public static void SetAttributeValue(this XmlNode input, string name, Guid? value) + { + XmlAttribute element = null; + //try + { + element = input.Attributes[name]; + } + //catch { } + if (element == null) + { + element = input.OwnerDocument.CreateAttribute(name); + input.Attributes.Append(element); + } + element.InnerText = value.ToStringOrEmpty(); + } + public static string GetValueAttribute(this XmlNode input, string name) + { + return input.GetValueAttribute(name, string.Empty); + } + + public static string GetValueAttribute(this XmlNode input, string name, string empty) + { + XmlAttribute element = null; + //try + { + if (input == null) return empty; + element = input.Attributes[name]; + + if (element != null) + return element.InnerText; + else + return empty; + } + //catch + //{ + // return empty; + //} + } + public static string GetValueAttribute(this XmlNode input, string name, string empty, XmlNamespaceManager namespaces) + { + XmlNode element = null; + //try + { + if (input == null) return empty; + element = input.SelectSingleNode(name, namespaces); + + if (element != null) + return element.InnerText; + else + return empty; + } + //catch + //{ + // return empty; + //} + } + public static string GetValue(this XmlNode input, string name) + { + return input.GetValue(name, String.Empty); + } + + public static DateTime GetValueDateTime(this XmlNode input, string name) + { + return XmlConvert.ToDateTime(input.GetValue(name, String.Empty), XmlDateTimeSerializationMode.RoundtripKind); + } + + public static string GetValue(this XmlNode input, string name, string empty) + { + var element = input.SelectSingleNode(name); + if (element == null) return empty; + return element.InnerText; + } + + public static string GetValue(this XmlNode input, string name, XmlNamespaceManager namespaces) + { + return input.GetValue(name, String.Empty, namespaces); + } + + public static string GetValue(this XmlNode input, string name, string empty, XmlNamespaceManager namespaces) + { + var element = input.SelectSingleNode(name, namespaces); + if (element == null) return empty; + return element.InnerText; + } + + public static string GetValueCData(this XmlNode input, string name) + { + return input.GetValueCData(name, string.Empty); + } + + public static string GetValueCData(this XmlNode input, string name, string empty) + { + var element = input.SelectSingleNode(name); + if (element == null) return empty; + if (element.FirstChild == null) return element.InnerText; + return element.FirstChild.InnerText; + } + + public static DateTime? GetValueDate(this XmlNode input, string name) + { + return input.GetValue(name, String.Empty).TryParseToDateXMLNullable(); + } + + public static DateTime GetValueDate(this XmlNode input, string name, DateTime empty) + { + return input.GetValue(name, String.Empty).TryParseToDateXMLNullable(empty); + } + + public static short GetValueShort(this XmlNode input, string name) + { + return input.GetValue(name, String.Empty).TryParseToShort(); + } + + public static bool GetValueBool(this XmlNode input, string name) + { + return input.GetValue(name, String.Empty).TryParseToBool(false); + } + public static bool GetValueBool(this XmlNode input, string name, bool empty) + { + return input.GetValue(name, String.Empty).TryParseToBool(empty); + } + public static Int32? GetValueInt32Nulable(this XmlNode input, string name) + { + return input.GetValue(name, String.Empty).TryParseToInt32Nulable(); + } + + public static Int32 GetValueInt32(this XmlNode input, string name) + { + return input.GetValue(name, String.Empty).TryParseToInt32(); + } + public static Int32 GetValueInt32(this XmlNode input, string name, int empty) + { + return input.GetValue(name, String.Empty).TryParseToInt32(empty); + } + public static byte GetValueByte(this XmlNode input, string name, byte empty) + { + return input.GetValue(name, String.Empty).TryParseToByte(empty); + } + public static decimal? GetValueDecimal(this XmlNode input, string name) + { + return input.GetValue(name, String.Empty).TryParseToDecimalNulable(); + } + public static decimal GetValueDecimal(this XmlNode input, string name, decimal empty) + { + return input.GetValue(name, String.Empty).TryParseToDecimal(empty); + } + public static Guid GetValueGuid(this XmlNode input, string name, Guid empty) + { + return input.GetValue(name, String.Empty).TryParseToGuid(empty); + } + + public static Guid GetValueGuid(this XmlNode input, string name) + { + return input.GetValue(name, String.Empty).TryParseToGuid(Guid.Empty); + } + public static Guid? GetValueGuidNullable(this XmlNode input, string name) + { + return input.GetValue(name, String.Empty).TryParseToGuidNulable(null); + } + + public static Guid GetValueAttributeGuid(this XmlNode input, string name) + { + return input.GetValueAttributeGuid(name, null) ?? Guid.Empty; + } + + public static Guid? GetValueAttributeGuid(this XmlNode input, string name, Guid? empty) + { + return input.GetValueAttribute(name, String.Empty).TryParseToGuidNulable(empty); + } + + public static Guid? GetValueAttributeGuid(this XmlNode input, string name, XmlNamespaceManager namespaces, Guid? empty) + { + return input.GetValueAttribute(name, String.Empty).TryParseToGuidNulable(empty); + } + + + public static int GetValueAttributeInt32(this XmlNode input, string name) + { + return input.GetValueAttribute(name, String.Empty).TryParseToInt32(0); + } + + public static int? GetValueAttributeInt32Nullable(this XmlNode input, string name) + { + return input.GetValueAttribute(name, String.Empty).TryParseToInt32Nulable(); + } + + public static double GetValueAttributeDouble(this XmlNode input, string name) + { + return GetValueAttributeDouble(input, name, 0); + } + + public static double GetValueAttributeDouble(this XmlNode input, string name, double @default) + { + return input.GetValueAttribute(name, String.Empty).TryParseToDouble(@default); + } + + public static double? GetValueAttributeDoubleNullable(this XmlNode input, string name) + { + return input.GetValueAttribute(name, String.Empty).TryParseToDoubleNulable(); + } + + public static long GetValueAttributeInt64(this XmlNode input, string name) + { + return input.GetValueAttribute(name, String.Empty).TryParseToInt64(0); + } + + public static long? GetValueAttributeInt64Nullable(this XmlNode input, string name) + { + return input.GetValueAttribute(name, String.Empty).TryParseToInt64Nulable(); + } + + public static ulong GetValueAttributeUInt64(this XmlNode input, string name) + { + return input.GetValueAttribute(name, String.Empty).TryParseToUInt64(0); + } + + public static ulong? GetValueAttributeUInt64Nullable(this XmlNode input, string name) + { + return input.GetValueAttribute(name, String.Empty).TryParseToUInt64Nulable(); + } + + public static bool GetValueAttributeBool(this XmlNode input, string name) + { + return input.GetValueAttribute(name, String.Empty).TryParseToBool(false); + } + public static bool GetValueAttributeBool(this XmlNode input, string name, bool empty) + { + return input.GetValueAttribute(name, String.Empty).TryParseToBool(empty); + } + + + public static string ToStringXMLShema(this DateTime dt) + { + if (dt == null) return String.Empty; + return dt.ToString("yyyy-MM-ddTHH:mm:ss"); + } + + public static string ToStringXMLShema(this DateTime? dt) + { + if (dt == null) return String.Empty; + return (dt ?? DateTime.UtcNow).ToString("yyyy-MM-ddTHH:mm:ss"); + } + + public static string ToStringSoutXML(this DateTime dt) + { + if (dt == null) return String.Empty; + return dt.ToString("yyyy-MM-dd"); + } + + public static string ToStringSoutXML(this DateTime? dt) + { + if (dt == null) return String.Empty; + return (dt ?? DateTime.UtcNow).ToString("yyyy-MM-dd"); + } + + public static double GetValueDouble_(this XmlNode input, string name) + { + double value; + if (double.TryParse(input.GetValue(name), NumberStyles.Any, ci.NumberFormat, out value)) + return value; + return double.MinValue; + } + public static double GetValueDouble_(this XmlNode input) + { + double value; + if (double.TryParse(input.InnerText, NumberStyles.Any, ci.NumberFormat, out value)) + return value; + return double.MinValue; + } + public static void ForEach(this XmlNodeList nodeList, Action action) + { + foreach (XmlNode node in nodeList) + { + action(node); + } + } + public static IEnumerable ToList(this XmlNodeList nodeList) + { + var resultList = new List(); + foreach (XmlNode node in nodeList) + { + resultList.Add(node); + } + return resultList; + } + public static IEnumerable Select(this XmlNodeList nodeList, Func action) + { + var resultList = new List(); + foreach (XmlNode node in nodeList) + { + resultList.Add(action(node)); + } + return resultList; + } + + /// + /// Get the node located along the .
+ /// If the element could not be found, the element itself and the path to it will be created. + ///
+ /// Root node + /// XPath to the node. XPath should only include tag names and separators. + /// Target child node + public static XmlNode GetOrRestoreByPath(this XmlNode xmlNodeRoot, string path) + { + XmlNode xmlNodeTarget = xmlNodeRoot.SelectSingleNode(path); + if (xmlNodeTarget != null) + { + return xmlNodeTarget; + } + + string[] pathItems = path.Trim('/').Split('/'); + + XmlNode xmlNodeCurrent = xmlNodeRoot; + + foreach (string pathItem in pathItems) + { + XmlNode xmlNodeItem = xmlNodeCurrent.SelectSingleNode(pathItem); + if (xmlNodeItem == null) + { + xmlNodeItem = xmlNodeCurrent.AppendChild((xmlNodeRoot.OwnerDocument ?? (XmlDocument)xmlNodeRoot).CreateElement(pathItem)); + } + + xmlNodeCurrent = xmlNodeItem; + } + + return xmlNodeCurrent; + } + + public static XmlNode RemoveAllEx(this XmlNode xmlNode) + { + xmlNode.RemoveAll(); + return xmlNode; + } + + public static ItemLog ModifyAttribute(this XmlNode input, ItemLog itemLog, string name, object? value) + { + itemLog = itemLog ?? new ItemLog(string.Empty, string.Empty, name); + itemLog.ValueNew = value?.ToString() ?? string.Empty; + + XmlAttribute element = input.Attributes[name]; + + if (element == null) + { + element = input.OwnerDocument.CreateAttribute(name); + input.Attributes.Append(element); + } + else + { + itemLog.ValueOld = element.InnerText; + } + + element.InnerText = itemLog.ValueNew; + + return itemLog.FillType(); + } + + public static ItemLog ModifyElement(this XmlNode input, ItemLog itemLog, string name, object value) + { + itemLog = itemLog ?? new ItemLog(string.Empty, string.Empty, name); + itemLog.ValueNew = value?.ToString() ?? string.Empty; + + var element = input.SelectSingleNode(name); + if (element == null) + { + element = input.OwnerDocument.CreateElement(name); + input.AppendChild(element); + } + else + { + itemLog.ValueOld = element.InnerText; + } + + element.InnerText = itemLog.ValueNew; + + return itemLog.FillType(); + } + + public static ItemLog ModifyElementCData(this XmlNode input, ItemLog itemLog, string name, string value) + { + itemLog = itemLog ?? new ItemLog(string.Empty, string.Empty, name); + itemLog.ValueNew = value?.ToString() ?? string.Empty; + + var element = input.SelectSingleNode(name); + if (element == null) + { + var root = input.OwnerDocument.CreateElement(name); + element = input.OwnerDocument.CreateNode(XmlNodeType.CDATA, name, null); + root.AppendChild(element); + input.AppendChild(root); + } + else + { + itemLog.ValueOld = GetValueCData(element, name); + } + + element.InnerText = itemLog.ValueNew; + + return itemLog.FillType(); + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Http/FluentHttpClient.cs b/LibCommon/Kit.Core.Helpers/Http/FluentHttpClient.cs new file mode 100644 index 0000000..d1dabd9 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Http/FluentHttpClient.cs @@ -0,0 +1,158 @@ +namespace Kit.Helpers +{ + using System; + using System.Collections.Generic; + using System.Net.Http; + using System.Text; + using System.Threading.Tasks; + + public class FluentHttpClient + { + private readonly Uri _requestUrl; + private Func> _successHandler = null; + private Func> _errorHandler = null; + private string _securityKey = null; + + private readonly List> _parameters = new List>(); + private readonly HttpMethod _httpMethod; + private string _postStringContent = string.Empty; + private string _postJsonContent = string.Empty; + + public FluentHttpClient(Uri requestUrl, HttpMethod httpMethod) + { + _requestUrl = requestUrl; + + if (httpMethod != HttpMethod.Get && httpMethod != HttpMethod.Post) throw new ArgumentOutOfRangeException("httpMethod", "only POST and GET methods supported"); + _httpMethod = httpMethod; + } + + #region prepare methods + + public FluentHttpClient WithParameterNullable(string name, TObject value) + { + if (value != null) + { + _parameters.Add(new KeyValuePair(name, value?.ToString())); + } + return this; + } + + public FluentHttpClient WithParameter(string name, TObject value) + { + _parameters.Add(new KeyValuePair(name, value?.ToString())); + return this; + } + + public FluentHttpClient WithStringContent(string content) + { + if (_httpMethod != HttpMethod.Post) throw new ArgumentOutOfRangeException("httpMethod", "only for POST method string content supported"); + _postStringContent = content; + return this; + } + + public FluentHttpClient WithJsonContent(TObject obj) + { + if (_httpMethod != HttpMethod.Post) throw new ArgumentOutOfRangeException("httpMethod", "only for POST method string content supported"); + _postJsonContent = obj.JsonSerialize(); + return this; + } + + public FluentHttpClient WithSecurityKey(string securityKey) + { + _securityKey = securityKey; + return this; + } + + public FluentHttpClient WithSuccessHandler(Func> responseHandler) + { + _successHandler = responseHandler; + return this; + } + + public FluentHttpClient WithErrorHandler(Func> errorHandler) + { + _errorHandler = errorHandler; + return this; + } + + #endregion + + #region finish methods + + public async Task ExecuteRequest() + { + Func> createRequestMethod; + if (_httpMethod == HttpMethod.Get) + { + // классический GET запрос + createRequestMethod = async x => await x.GetAsync(_requestUrl.SetQueryParameters(_parameters)).ConfigureAwait(false); + } + else if (!string.IsNullOrWhiteSpace(_postStringContent)) + { + // запрос с собственным содержанием тела + createRequestMethod = async x => await x.PostAsync(_requestUrl.SetQueryParameters(_parameters), new StringContent(_postStringContent)).ConfigureAwait(false); + } + else if (!string.IsNullOrWhiteSpace(_postJsonContent)) + { + // JSON запрос + createRequestMethod = async x => await x.PostAsync(_requestUrl.SetQueryParameters(_parameters), new StringContent(_postJsonContent, Encoding.UTF8, "application/json")).ConfigureAwait(false); + } + else + + { + // классический POST запрос + createRequestMethod = async y => await y.PostAsync(_requestUrl, new FormUrlEncodedContent(_parameters)).ConfigureAwait(false); + } + + return await HttpHelper.ExecuteRequest(createRequestMethod, _successHandler, _errorHandler, _securityKey).ConfigureAwait(false); + } + + public async Task ExecuteStreamRequest() + { + if (_httpMethod == HttpMethod.Get) + { + return await HttpHelper.ExecuteGetStreamUrl(_requestUrl, _parameters, null!, null!, _securityKey).ConfigureAwait(false); + } + return await HttpHelper.ExecutePostStreamUrl(_requestUrl, _parameters, null!, null!, _securityKey).ConfigureAwait(false); + } + + #endregion + } + + public static class FluentHttpClient_Extensions + { + public static FluentHttpClient PrepareHttpGet(this Uri requestUri) + { + return new FluentHttpClient(requestUri, HttpMethod.Get); + } + + public static FluentHttpClient PrepareHttpPost(this Uri requestUri) + { + return new FluentHttpClient(requestUri, HttpMethod.Post); + } + } +} + +//_createTokenUrl.HttpRequest() +// .AddHeader("header1", "header1_value") +// .AddCookie("cookie1", "cookie1_value") +// .AddQueryStringParameter("qsp", "qsp_value") +// .AddParameter("lifeTimeInSeconds", lifeTimeInSeconds) +// .AddParameter("data", data) +// .AddParameters(new List> +// { +// new KeyValuePair("k1", "v1"), +// new KeyValuePair("k2", "v2") +// }) +// .AddParameters(new { a = 5, b = "abc" }) +// .SendPost() +// .OnSuccess(response => +// { +// // HttpResponseMessage response +// }) +// .OnError(ex => +// { +// // exception handling +// }) +// .ConvertToObject() +// .ConvertToCollection(); \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/HttpContext.cs b/LibCommon/Kit.Core.Helpers/HttpContext.cs new file mode 100644 index 0000000..7e554fa --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/HttpContext.cs @@ -0,0 +1,68 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; + +namespace Kit.Helpers +{ + public static class HttpContextCore + { + private static IServiceProvider _serviceProvider; + private static bool _notWebApp; + + public static void Configure(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + _notWebApp = false; + } + + public static void ConfigureNotWebApp() + { + _notWebApp = true; + } + + public static HttpContext Current + { + get + { + if (_notWebApp) + { + return null; + } + // var factory2 = ServiceProvider.GetService(); + object factory = _serviceProvider.GetService(typeof(IHttpContextAccessor)); + + // Microsoft.AspNetCore.Http.HttpContextAccessor fac =(Microsoft.AspNetCore.Http.HttpContextAccessor)factory; + HttpContext context = ((HttpContextAccessor)factory).HttpContext; + // context.Response.WriteAsync("Test"); + + return context; + } + } + + private static readonly string _contextHostUrl = "HostUrl"; + + public static string GetHostUrl(this HttpContext context) + { + context.Items[_contextHostUrl] = context.Items[_contextHostUrl] as string ?? context.GenerateHostUrl(); + return context.Items[_contextHostUrl]!.ToString()!; + } + public static string GetRootUrl(this HttpContext context) + { + return $"{GetHostUrl(context)}{context.Request.PathBase}"; + } + + private static string GenerateHostUrl(this HttpContext context, bool checkForwardedProto = true) + { + HttpRequest request = context.Request; + + // Формируем RootUrl + string scheme = request.Scheme; + + if (checkForwardedProto && request.Headers.TryGetValue("X-Forwarded-Proto", out StringValues forwardedProtoValues) && forwardedProtoValues.Count > 0) + { + scheme = forwardedProtoValues.First()!; + } + + return $"{scheme}://{request.Host}"; + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/HttpHelper.cs b/LibCommon/Kit.Core.Helpers/HttpHelper.cs new file mode 100644 index 0000000..73234f0 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/HttpHelper.cs @@ -0,0 +1,559 @@ +using Microsoft.Extensions.Primitives; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using Microsoft.AspNetCore.Http; + +namespace Kit.Helpers +{ + + + public class FileNameStream : IDisposable + { + public string FileName { get; set; } + public Stream Stream { get; set; } + + public void Dispose() + { + if (Stream != null) Stream.Dispose(); + } + } + public class HttpRequestException : Exception + { + public HttpRequestException(HttpResponseMessage response, string responseBody) + : base(response.ReasonPhrase) + { + this.Response = response; + this.ResponseBody = responseBody; + } + public string ResponseBody { get; protected set; } + public HttpResponseMessage Response { get; protected set; } + } + + public static class HttpHelper_Extensions + { + public static Uri ToUri(this string url) + { + return new Uri(url); + } + + public static string Append(this string url, params string[] paths) + { + if (url == null) url = string.Empty; + string[] urlParts = url.Split('?'); + url = paths.Aggregate(urlParts[0], (current, path) => string.Format("{0}/{1}", current.TrimEnd('/'), path.TrimStart('/'))); + if (urlParts.Length > 1) url += "?" + urlParts[1]; + return url; + } + + public static Uri Append(this Uri uri, params string[] paths) + { + return new Uri(uri.AbsoluteUri.Append(paths)); + } + + public static Uri SetQueryParameters(this Uri uri, IEnumerable> parameters) + { + var ub = new UriBuilder(uri); + NameValueCollection nvc = HttpUtility.ParseQueryString(ub.Query); + parameters.ToList().ForEach(x => + { + nvc[x.Key] = x.Value; + }); + ub.Query = nvc.ToString(); + return ub.Uri; + } + + public static string AbsoluteUrl(this HttpRequest request, bool checkForwardedProto = false) + { + string scheme = request.Scheme; + string host = request.Host.ToUriComponent(); + string pathBase = request.PathBase.ToUriComponent(); + string path = request.Path.ToUriComponent(); + string queryString = request.QueryString.ToUriComponent(); + + if (checkForwardedProto && request.Headers.TryGetValue("X-Forwarded-Proto", out StringValues forwardedProtoValues) && forwardedProtoValues.Count > 0) + { + scheme = forwardedProtoValues.First(); + } + + return string.Concat(scheme, "://", host, pathBase, path, queryString); + } + + public static string GetUserToken(this HttpRequest request) + { + string _userTokenNameContent = "userToken"; + string _userTokenNameHeader = "RiskProf_UserToken"; + + string? userTokenCookie = request.Cookies[_userTokenNameHeader]; + + string userToken = request.Query[_userTokenNameContent]; + if (string.IsNullOrWhiteSpace(userToken)) + { + userToken = request.Headers[_userTokenNameHeader]; + } + if (string.IsNullOrWhiteSpace(userToken) && request.HasFormContentType) + { + userToken = request.Form[_userTokenNameContent]; + } + + if (string.IsNullOrWhiteSpace(userToken) == false) + { + return userToken; + } + + return userTokenCookie; + } + public static int? GetCabinetId(this HttpRequest request) + { + string _cabinetIdNameContent = "cabinetId"; + string _cabinetIdNameHeader = "RiskProf_CabinetId"; + + string? cabinetIdCookie = request.Cookies[_cabinetIdNameHeader]; + + string cabinetId = request.Query[_cabinetIdNameContent]; + if (string.IsNullOrWhiteSpace(cabinetId)) + { + cabinetId = request.Headers[_cabinetIdNameHeader]; + } + if (string.IsNullOrWhiteSpace(cabinetId) && request.HasFormContentType) + { + cabinetId = request.Form[_cabinetIdNameContent]; + } + + if (string.IsNullOrWhiteSpace(cabinetId) == false) + { + return cabinetId.TryParseToInt32(); + } + + return cabinetIdCookie.TryParseToInt32(); + } + public static string PathAndQuery(this HttpRequest request) + { + return string.Concat( + request.PathBase.ToUriComponent(), + request.Path.ToUriComponent(), + request.QueryString.ToUriComponent()); + } + + public static StringValues GetValue(this HttpRequest request, string name) + { + var result = StringValues.Empty; + if (request.HasFormContentType) + { + result = request.Form[name]; + } + if (result == StringValues.Empty) + { + result = request.Query[name]; + } + return result; + } + + public static StringValues GetValueWithoutCheck(this HttpRequest request, string name) + { + StringValues result = request.Form[name]; + if (result == StringValues.Empty) + { + result = request.Query[name]; + } + return result; + } + + /// + /// Метод чтения json модели из тела запроса. Поток запроса можно считать только один раз + /// + /// + /// + /// + /// + public static T TryReadJsonModel(this HttpRequest request) + { + try + { + using var reader = new StreamReader(request.Body); + string body = reader.ReadToEndAsync().Result; + + return body.JsonDeserialize(); + } + catch (Exception ex) + { + throw new InvalidOperationException("Не удалось прочитать json модель из запроса", ex); + } + } + + public static void WriteStream(this HttpResponse response, Stream stream, string fileName, string contentType = null) + { + //response.Clear(); + //response.ContentType = string.IsNullOrWhiteSpace(contentType) ? "application/octet-stream" : contentType; + //response.Headers.Add("Content-Disposition", $"attachment; filename={fileName}"); + //response.SendFileAsync() + //stream.CopyTo(response.OutputStream); + //response.Flush(); + //response.End(); + } + + public static void WriteStream(this HttpResponse response, string filePath, string fileName, string contentType = null) + { + response.Clear(); + response.ContentType = string.IsNullOrWhiteSpace(contentType) ? "application/octet-stream" : contentType; + response.Headers.Add("Content-Disposition", $"attachment; filename={fileName}"); + response.SendFileAsync(filePath); + } + + public static string ReadAuthHeader(this IHeaderDictionary headers) + { + return headers["X-Requested-Token"].FirstOrDefault() ?? String.Empty; + } + + public static void WriteStatus(this HttpResponse response, HttpStatusCode statusCode, string message = null, string responseBody = null) + { + if (response == null) return; + + byte[] bytes = Encoding.ASCII.GetBytes(message ?? string.Empty); + response.StatusCode = (int)statusCode; + response.Body.WriteAsync(bytes); + } + + // для отправки сообщений об ошибках в браузер при ajax-вызовах + public static void WriteError(this HttpResponse response, string message) + { + response.WriteStatus(HttpStatusCode.InternalServerError, message); + } + + public static void WriteException(this HttpResponse response, Exception ex) + { + if (ex == null) return; + response.WriteStatus(HttpStatusCode.InternalServerError, ex.Message, ex.GetInfoAsPlainText()); + } + public static void SetAuthHeader(this HttpClient client, string securityKey) + { + client.DefaultRequestHeaders.Add("X-Requested-Token", securityKey); // = new AuthenticationHeaderValue() + } + + public static string ReadAuthHeader(this HttpRequestHeaders headers) + { + return headers.GetValues("X-Requested-Token").FirstOrDefault() ?? String.Empty; + } + } + + public static class HttpHelper + { + public static async Task ExecuteGetUrl(Uri uri + , IEnumerable> parameters + , Func> responseHandler = null + , Func> errorHandler = null + , string securityKey = null + ) + { + string responseBody = await HttpHelper.ExecuteGetUrl(uri, parameters, responseHandler, errorHandler, securityKey).ConfigureAwait(false); + return responseBody.JsonDeserialize(); + } + + public static async Task ExecuteGetUrl(Uri uri + , IEnumerable> parameters + , Func> responseHandler = null + , Func> errorHandler = null + , string securityKey = null + ) + { + return await HttpHelper.ExecuteRequest(async x => await x.GetAsync(uri.SetQueryParameters(parameters)).ConfigureAwait(false), responseHandler, errorHandler, securityKey).ConfigureAwait(false); + } + + public static async Task ExecuteGetStreamUrl(Uri uri + , IEnumerable> parameters + , Func> responseHandler = null + , Func> errorHandler = null + , string securityKey = null + ) + { + return await HttpHelper.ExecuteStreamRequest(async x => await x.GetAsync(uri.SetQueryParameters(parameters)).ConfigureAwait(false), responseHandler, errorHandler, securityKey).ConfigureAwait(false); + } + public static async Task ExecutePostStreamUrl(Uri uri + , IEnumerable> parameters + , Func> responseHandler = null + , Func> errorHandler = null + , string securityKey = null + ) + { + return await HttpHelper.ExecuteStreamRequest(async x => await x.PostAsync(uri, new FormUrlEncodedContent(parameters)).ConfigureAwait(false), responseHandler, errorHandler, securityKey).ConfigureAwait(false); + } + public static async Task ExecutePostFileStreamUrl(Uri uri + , IEnumerable> parameters + , Func> responseHandler = null + , Func> errorHandler = null + , string securityKey = null + ) + { + return await HttpHelper.ExecuteFileStreamRequest(async x => await x.PostAsync(uri, new FormUrlEncodedContent(parameters)).ConfigureAwait(false), responseHandler, errorHandler, securityKey).ConfigureAwait(false); + } + + public static async Task ExecutePostUrl(Uri uri + , IEnumerable> parameters = null + , Func> responseHandler = null + , Func> errorHandler = null + , string securityKey = null + ) + { + return await HttpHelper.ExecuteRequest(async x => await x.PostAsync(uri, new FormUrlEncodedContent(parameters)).ConfigureAwait(false), responseHandler, errorHandler, securityKey).ConfigureAwait(false); + } + + public static async Task ExecutePostUrl(Uri uri + , IEnumerable> parameters = null + , Func> responseHandler = null + , Func> errorHandler = null + , string securityKey = null + ) + { + string responseBody = await HttpHelper.ExecuteRequest(async x => await x.PostAsync(uri, new FormUrlEncodedContent(parameters)).ConfigureAwait(false), responseHandler, errorHandler, securityKey).ConfigureAwait(false); + return responseBody.JsonDeserialize(); + } + + public static async Task ExecutePostString(Uri uri + , string content = null + , Func> responseHandler = null + , Func> errorHandler = null + , string securityKey = null + ) + { + return await HttpHelper.ExecuteRequest(async x => await x.PostAsync(uri, new StringContent(content)).ConfigureAwait(false), responseHandler, errorHandler, securityKey).ConfigureAwait(false); + } + + public static async Task ExecutePostString(Uri uri + , string content = null + , Func> responseHandler = null + , Func> errorHandler = null + , string securityKey = null + ) + { + string responseBody = await HttpHelper.ExecuteRequest(async x => await x.PostAsync(uri, new StringContent(content)).ConfigureAwait(false), responseHandler, errorHandler, securityKey).ConfigureAwait(false); + return responseBody.JsonDeserialize(); + } + + public static async Task ExecutePostObject(Uri uri + , TObject obj + , Func> responseHandler = null + , Func> errorHandler = null + , string securityKey = null + ) + { + string requestBody = obj.JsonSerialize(); + return await HttpHelper.ExecutePostString(uri, requestBody, responseHandler, errorHandler, securityKey).ConfigureAwait(false); + } + + public static async Task ExecutePostObject(Uri uri + , TRequest obj + , Func> responseHandler = null + , Func> errorHandler = null + , string securityKey = null + ) + { + string requestBody = obj.JsonSerialize(); + string responseBody = await HttpHelper.ExecutePostString(uri, requestBody, responseHandler, errorHandler, securityKey).ConfigureAwait(false); + return responseBody.JsonDeserialize(); + } + public static async Task ExecutePostFiles(Uri uri + , IEnumerable> fileStreamNames + , IEnumerable> parameters = null + , Func> responseHandler = null + , Func> errorHandler = null + , string securityKey = null + ) + { + var formData = new MultipartFormDataContent(); + if (fileStreamNames.IsNullOrEmpty() == false) + { + int i = 1; + foreach (KeyValuePair fileStreamName in fileStreamNames) + { + HttpContent fileStreamContent = new StreamContent(fileStreamName.Key); + formData.Add(fileStreamContent, string.Concat("file_", i++.ToString()), fileStreamName.Value); + } + } + + if (parameters != null) + { + foreach (var keyValuePair in parameters) + { + HttpContent stringContent = new StringContent(keyValuePair.Value); + formData.Add(stringContent, keyValuePair.Key); + } + } + + string responseBody = await HttpHelper.ExecuteRequest(async x => await x.PostAsync(uri, formData).ConfigureAwait(false), responseHandler, errorHandler, securityKey).ConfigureAwait(false); + return responseBody.JsonDeserialize(); + } + public static async Task ExecutePostFiles(Uri uri + , IEnumerable files + , IEnumerable> parameters = null + , Func> responseHandler = null + , Func> errorHandler = null + , string securityKey = null + ) + { + var formData = new MultipartFormDataContent(); + if (files != null) + { + int i = 1; + foreach (FileStream fileStream in files) + { + HttpContent fileStreamContent = new StreamContent(fileStream); + formData.Add(fileStreamContent, string.Concat("file_", i++.ToString()), fileStream.Name); + } + } + + if (parameters != null) + { + foreach (var keyValuePair in parameters) + { + HttpContent stringContent = new StringContent(keyValuePair.Value); + formData.Add(stringContent, keyValuePair.Key); + } + } + + string responseBody = await HttpHelper.ExecuteRequest(async x => await x.PostAsync(uri, formData).ConfigureAwait(false), responseHandler, errorHandler, securityKey).ConfigureAwait(false); + return responseBody.JsonDeserialize(); + } + public static async Task ExecutePostStreamUrl(Uri uri + , IEnumerable files + , IEnumerable> parameters + , Func> responseHandler = null + , Func> errorHandler = null + , string securityKey = null + ) + { + var formData = new MultipartFormDataContent(); + if (files != null) + { + int i = 1; + foreach (FileStream fileStream in files) + { + HttpContent fileStreamContent = new StreamContent(fileStream); + formData.Add(fileStreamContent, string.Concat("file_", i++.ToString()), fileStream.Name); + } + } + if (parameters != null) + { + foreach (var keyValuePair in parameters) + { + HttpContent stringContent = new StringContent(keyValuePair.Value); + formData.Add(stringContent, keyValuePair.Key); + } + } + return await HttpHelper.ExecuteStreamRequest(async x => await x.PostAsync(uri, formData).ConfigureAwait(false), responseHandler, errorHandler, securityKey).ConfigureAwait(false); + } + + public static async Task ExecuteRequest( + Func> createRequest + , Func> responseHandler = null + , Func> errorHandler = null + , string userToken = null + ) + { + using (var client = new HttpClient()) + { + //client.BaseAddress = new Uri("https://localhost:44354/api/file/createFolder"); + client.Timeout = TimeSpan.FromSeconds(3600); + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + + // add auth header + client.SetAuthHeader(userToken); + try + { + + using (HttpResponseMessage response = await createRequest(client).ConfigureAwait(false)) + { + // считываем содержимое ответа + string responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + if (response.Content != null) response.Content.Dispose(); + + // если что-то произошло на стороне сервера + if (!response.IsSuccessStatusCode) + { + if (errorHandler != null) return await errorHandler(response, responseBody).ConfigureAwait(false); + throw new HttpRequestException(response, responseBody); + } + + return responseHandler != null + ? await responseHandler(response, responseBody).ConfigureAwait(false) + : responseBody; + } + } + catch (Exception ex) + { + throw; + } + } + } + + public static async Task ExecuteStreamRequest( + Func> createRequest + , Func> responseHandler = null + , Func> errorHandler = null + , string securityKey = null + ) + { + using (var client = new HttpClient()) + { + client.Timeout = TimeSpan.FromSeconds(3600); + + // add auth header + client.SetAuthHeader(securityKey); + HttpResponseMessage response = await createRequest(client).ConfigureAwait(false); + + // считываем содержимое ответа + Stream responseBody = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + string responseBodyString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + + // если что-то произошло на стороне сервера + if (!response.IsSuccessStatusCode) + { + if (errorHandler != null) return await errorHandler(response, responseBody).ConfigureAwait(false); + throw new HttpRequestException(response, responseBodyString); + } + + return responseHandler != null + ? await responseHandler(response, responseBody).ConfigureAwait(false) + : responseBody; + } + } + + public static async Task ExecuteFileStreamRequest( + Func> createRequest + , Func> responseHandler = null + , Func> errorHandler = null + , string securityKey = null + ) + { + using (var client = new HttpClient()) + { + client.Timeout = TimeSpan.FromSeconds(3600); + // add auth header + client.SetAuthHeader(securityKey); + HttpResponseMessage response = await createRequest(client).ConfigureAwait(false); + string fileName = response.Content.Headers.ContentDisposition.FileName; + + // считываем содержимое ответа + Stream responseBody = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + string responseBodyString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + + // если что-то произошло на стороне сервера + if (!response.IsSuccessStatusCode) + { + if (errorHandler != null) return await errorHandler(response, responseBody).ConfigureAwait(false); + throw new HttpRequestException(response, responseBodyString); + } + return responseHandler != null + ? await responseHandler(response, responseBody).ConfigureAwait(false) + : new FileNameStream { Stream = responseBody, FileName = fileName }; + } + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/JsonHelper.cs b/LibCommon/Kit.Core.Helpers/JsonHelper.cs new file mode 100644 index 0000000..515689a --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/JsonHelper.cs @@ -0,0 +1,106 @@ +using System.Globalization; +using System.Text.Json.Serialization; +using System.Text.Json; +using System; +using System.Text.RegularExpressions; + +namespace Kit.Helpers +{ + using System.Text.Encodings.Web; + using System.Text.Json; + using System.Text.Unicode; + using System.Threading.Tasks; + + public static class JsonHelper + { + public static TObject? JsonDeserialize(this string json) + { + if (json == null || string.IsNullOrWhiteSpace(json)) return default(TObject); + var options = new JsonSerializerOptions(); + options.Converters.Add(new DateTimeIsoFormatConverter()); + options.PropertyNameCaseInsensitive = true; + return JsonSerializer.Deserialize(json, options); + } + + public static string JsonSerialize(this TObject serializeObject, bool enableCyrillic = false, bool includeFields = false, bool writeIntended = false, JsonNamingPolicy? propertyNamingPolicy = null) + { + if (serializeObject == null) return "{}"; + + var options = new JsonSerializerOptions + { + IncludeFields = includeFields, + Encoder = enableCyrillic ? JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.Cyrillic) : JavaScriptEncoder.Create(UnicodeRanges.BasicLatin), + Converters = { new DateTimeIsoFormatConverter() }, + WriteIndented = writeIntended, + }; + + if (propertyNamingPolicy != null) + { + options.PropertyNamingPolicy = propertyNamingPolicy; + } + + string result = JsonSerializer.Serialize(serializeObject, options); + + return result; + } + + public static async Task JsonDeserialize(this Task task) + { + string json = await task.ConfigureAwait(false); + return await Task.Run(() => json.JsonDeserialize()).ConfigureAwait(false); + } + } +} +public class DateTimeIsoFormatConverter : JsonConverter +{ + private readonly string format = "yyyy-MM-ddTHH:mm"; + + public DateTimeIsoFormatConverter() { } + + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + string item = reader.GetString(); + + if (DateTime.TryParseExact(item, this.format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out DateTime result)) + { + return result; + } + + for (int i = 1; i < 10; i++) + { + string format_item = "yyyy-MM-ddTHH:mm:ss." + new string('f', i); + if (DateTime.TryParseExact(item, format_item, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out DateTime result_item)) + { + return result_item; + } + if (DateTime.TryParseExact(item, format_item + "Z", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out result_item)) + { + return result_item; + } + if (DateTime.TryParseExact(item, format_item + "zzz", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out result_item)) + { + return result_item; + } + } + + Match match = Regex.Match(item, @"\/Date\((\d+)\)\/"); + if (match.Success) + { + long milliseconds = long.Parse(match.Groups[1].Value); + return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(milliseconds); + } + + throw new FormatException(); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + ArgumentNullException.ThrowIfNull(writer, nameof(writer)); + + writer.WriteStringValue(value + .ToUniversalTime() + .ToString( + this.format, + CultureInfo.InvariantCulture)); + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Kit.Core.Helpers.csproj b/LibCommon/Kit.Core.Helpers/Kit.Core.Helpers.csproj new file mode 100644 index 0000000..5078041 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Kit.Core.Helpers.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + diff --git a/LibCommon/Kit.Core.Helpers/Log/CallStackLog.cs b/LibCommon/Kit.Core.Helpers/Log/CallStackLog.cs new file mode 100644 index 0000000..dbb29f1 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Log/CallStackLog.cs @@ -0,0 +1,222 @@ +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Linq; +using System.Text; + +namespace Kit.Helpers.Log +{ + public interface ICallStackLog + { + string LogMessage { get; } + ICallStackLog NextLevel(); + void FinishLevel(); + void BeginCall(string message); + void EndCall(); + } + + public class EmptyCallStackLog : ICallStackLog + { + public string LogMessage + { + get { return ""; } + } + + public ICallStackLog NextLevel() + { + return CallStackLog.Empty; + } + + public void FinishLevel() + { + return; + } + + public void BeginCall(string message) + { + return; + } + + public void EndCall() + { + return; + } + } + + public class CallStackLog : ICallStackLog + { + protected ConcurrentDictionary Logs { get; } + protected ConcurrentBag ChildCallStackLogs { get; set; } + protected readonly int _level; + protected readonly string _path; + protected string _currentCall; + protected int _number; + protected readonly Stopwatch _stopwatch = new Stopwatch(); + public static ICallStackLog Empty { get { return new EmptyCallStackLog(); } } + + protected string Number + { + get + { + return _number.ToString("D5"); + } + } + public CallStackLog() + { + Logs = new ConcurrentDictionary(); + ChildCallStackLogs = new ConcurrentBag(); + _level = 0; + _path = ""; + _number = 1; + } + + public CallStackLog(int level, string path) + { + Logs = new ConcurrentDictionary(); + ChildCallStackLogs = new ConcurrentBag(); + _level = level; + _path = path; + _number = 1; + } + + public string LogMessage + { + get + { + StringBuilder stringBuilder = new StringBuilder(); + foreach (var logLine in Logs.OrderBy(x => x.Key)) + { + string time = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:0.000}", logLine.Value / 1000.0f); + + stringBuilder.AppendLine(logLine.Key + $". Time - {time} ms"); + } + return stringBuilder.ToString(); + } + } + public ConcurrentDictionary GetLogs + { + get { + return this.Logs; + } + } + + public ICallStackLog NextLevel() + { + var childCallStackLog = new CallStackLog(_level + 1, _path + this.Number + "."); + ChildCallStackLogs.Add(childCallStackLog); + return childCallStackLog; + } + + public void FinishLevel() + { + foreach (CallStackLog childCallStackLog in ChildCallStackLogs) + { + lock (Logs) + { + foreach (var logLine in childCallStackLog.Logs) + { + if (Logs.ContainsKey(logLine.Key)) + { + Logs[logLine.Key] += logLine.Value; + } + else + { + Logs[logLine.Key] = logLine.Value; + } + } + } + } + + ChildCallStackLogs = new ConcurrentBag(); + } + + public void BeginCall(string message) + { + _currentCall = message; + _stopwatch.Restart(); + } + + public void EndCall() + { + _stopwatch.Stop(); + lock (Logs) + { + long ticksByMicrosecond = Stopwatch.Frequency / (1000L * 1000L); + long microSeconds = _stopwatch.ElapsedTicks / ticksByMicrosecond; + Logs.TryAdd($"{_path}{this.Number} {_currentCall.ToStringOrEmpty()}", microSeconds); + _number++; + } + } + } + + public class CallStackLogFile : ICallStackLog + { + protected object _lock = new object(); + protected ConcurrentBag ChildCallStackLogs { get; set; } + protected readonly int _level; + protected readonly string _path; + protected string _currentCall; + protected int _number; + protected readonly Stopwatch _stopwatch = new Stopwatch(); + protected readonly StreamWriter _writer; + public static ICallStackLog Empty { get { return new EmptyCallStackLog(); } } + + protected string Number + { + get + { + return _number.ToString("D5"); + } + } + public CallStackLogFile(StreamWriter writer) + { + _writer = writer; + ChildCallStackLogs = new ConcurrentBag(); + _level = 0; + _path = ""; + _number = 1; + } + + public CallStackLogFile(StreamWriter writer, int level, string path) + { + _writer = writer; + ChildCallStackLogs = new ConcurrentBag(); + _level = level; + _path = path; + _number = 1; + } + + public string LogMessage => string.Empty; + + public ICallStackLog NextLevel() + { + var childCallStackLog = new CallStackLogFile(_writer, _level + 1, _path + this.Number + "."); + ChildCallStackLogs.Add(childCallStackLog); + return childCallStackLog; + } + + public void FinishLevel() + { + ChildCallStackLogs = new ConcurrentBag(); + } + + public void BeginCall(string message) + { + _currentCall = message; + _stopwatch.Restart(); + } + + public void EndCall() + { + _stopwatch.Stop(); + lock (_lock) + { + long microSeconds = _stopwatch.ElapsedTicks / (Stopwatch.Frequency / (1000L * 1000L)); + + string time = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:0.000}", microSeconds / 1000.0f); + + _writer?.WriteLine($"{_path}{this.Number} {_currentCall.ToStringOrEmpty()}. Time - {time} ms"); + _number++; + } + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Log/CallStackLogDB.cs b/LibCommon/Kit.Core.Helpers/Log/CallStackLogDB.cs new file mode 100644 index 0000000..b2a5a02 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Log/CallStackLogDB.cs @@ -0,0 +1,44 @@ +namespace Kit.Helpers.Log +{ + public class CallStackLogDB: CallStackLog, IDisposable + { + protected string _sessionKey; + protected LogMessageType _type; + protected string _target; + protected string _action; + protected DateTime _dateCreated; + private bool disposed = false; + public CallStackLogDB(string sessionKey, LogMessageType type, string target, string action):base() + { + _sessionKey = sessionKey; + _type = type; + _target = target; + _action = action; + _dateCreated = DateTime.UtcNow; + } + + public void Dispose() + { + Dispose(true); + // подавляем финализацию + GC.SuppressFinalize(this); + + + } + + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + this.Logs.TryAdd($"Full Time", Convert.ToInt64((DateTime.UtcNow - _dateCreated).TotalMilliseconds)); + + Kit.Helpers.Log.LoggerDB.Commit(this._sessionKey, this._type, this._target, this._action, _dateCreated, this.Logs.ToDictionary(x => x.Key, x => x.Value)); + } + // освобождаем неуправляемые объекты + disposed = true; + } + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Log/Logger.cs b/LibCommon/Kit.Core.Helpers/Log/Logger.cs new file mode 100644 index 0000000..11d2f45 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Log/Logger.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Mail; +using System.Text; +using Microsoft.Extensions.Configuration; + +namespace Kit.Helpers.Log +{ + public enum LogMessageType + { + Debug = 1, + Info, + Warn, + Error, + } + + public static class Logger + { + private static string _pathLogFolder; + private static string _emailas; + private static bool _isSendEmail; + public static void Configure(IConfiguration configuration) + { + var config = new ConfigurationHelper(configuration, "Log"); + _pathLogFolder = config.GetAppSettingValue("PathFolder"); + + if (_pathLogFolder.IsNullOrEmpty() || Directory.Exists(_pathLogFolder) == false) + _pathLogFolder = Path.GetTempPath(); + + _isSendEmail = config.GetAppSettingValue("IsSendMail").TryParseToBool(false); + _emailas = config.GetAppSettingValue("Emails"); + } + + public static string GetCurrentFullFileName() + { + var dt = DateTime.UtcNow; + return Path.Combine(_pathLogFolder, String.Format("log_{0}_{1}_{2}.log", dt.Day, dt.Month, dt.Year)); + } + + public static string GetNameMessageType(LogMessageType type) + { + switch (type) + { + case LogMessageType.Debug: + return "Дебаг"; + case LogMessageType.Error: + return "Ошибка"; + case LogMessageType.Info: + return "Информация"; + case LogMessageType.Warn: + return "Предупреждение"; + } + return String.Empty; + } + + public static void Log(LogMessageType type, string log) + { + string path = GetCurrentFullFileName(); + using (var file = File.AppendText(path)) + { + var str = String.Format("{0}:({1}) {2}" + Environment.NewLine, DateTime.UtcNow, GetNameMessageType(type), log); + file.WriteLine(str); + if (_isSendEmail && type == LogMessageType.Error) + SendMail(new StringBuilder(str)); + } + } + + public static void Log(LogMessageType type, string log, params object[] args) + { + Log(type, String.Format(log, args)); + } + + public static void LogError(string header, string error) + { + using (var file = File.AppendText(GetCurrentFullFileName())) + { + file.WriteLine(String.Format("{0}:({1}) {2}", DateTime.UtcNow, GetNameMessageType(LogMessageType.Error), header)); + file.WriteLine(String.Format("Trace: {0}" + Environment.NewLine, error)); + } + } + + + public static void LogInfo(string log, params object[] args) + { + Log(LogMessageType.Info, String.Format(log, args)); + } + + public static void LogWarn(string log, params object[] args) + { + Log(LogMessageType.Warn, String.Format(log, args)); + } + + + public static void SendMail(StringBuilder errorText) + { + List gettersMailMessages = new List(); + List Getters = new List(); + + foreach (var mail in _emailas.Replace(",", ";").Split(';')) + { + Getters.Add(mail); + } + + if (!string.IsNullOrEmpty(errorText.ToString())) + { + + foreach (var getter in Getters) + { + var mail = new MailMessage + { + From = new MailAddress("noreply@aetalon.ru", "Ошибка Эталон"), + Subject = "Ошибка Эталон", + IsBodyHtml = false, + Body = errorText.ToString() + }; + + mail.Body = mail.Body.Replace("_EMAIL_RECEIVER_", Base64Encode(errorText.ToString())); + mail.To.Add(getter); + gettersMailMessages.Add(mail); + } + } + + + using (var smtp = new SmtpClient()) + { + foreach (var mail in gettersMailMessages) + { + try + { + smtp.Send(mail); + } + catch (Exception e) + { + + } + } + + } + + } + + public static string Base64Encode(string plainText) + { + var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText); + return System.Convert.ToBase64String(plainTextBytes); + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Log/LoggerDB.cs b/LibCommon/Kit.Core.Helpers/Log/LoggerDB.cs new file mode 100644 index 0000000..8d926d7 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Log/LoggerDB.cs @@ -0,0 +1,137 @@ +using Microsoft.Extensions.Configuration; +using System.Collections.Concurrent; + +namespace Kit.Helpers.Log +{ + + public enum TypeInfo { Debug = 1, Info, Warn, Error, } + + public class LoggerKey : IDisposable + { + private bool disposed = false; + public Guid Key { get; set; } + public void Dispose() + { + + Dispose(true); + // подавляем финализацию + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + LoggerDB.Commit(this); + } + // освобождаем неуправляемые объекты + disposed = true; + } + } + + // Деструктор + ~LoggerKey() + { + Dispose(false); + } + } + + public class LoggerItems + { + public string SessionKey { get; set; } + public LogMessageType LogMessageType { get; set; } + public DateTime DateCreated { get; set; } + public string Target { get; set; } + public string Action { get; set; } + public string Message { get; set; } + public double Duration { get; set; } + } + + public static class LoggerDB + { + private static string _connectionString; + private static bool _isRun; + + private static ConcurrentDictionary _items = new ConcurrentDictionary(); + + public static void Configure(IConfiguration configuration) + { + _connectionString = "";// new ConfigurationHelper("Logger") ConfigurationManager.ConnectionStrings["Logger.Db"].ConnectionString; + var config = new ConfigurationHelper(configuration, "LogDb"); + _isRun = config.GetAppSettingValue("IsRun").TryParseToBool(false); + } + + public static LoggerKey Log(string sessionKey, LogMessageType type, string target, string action, string message) + { + return Log(sessionKey, type, DateTime.UtcNow, target, action, message); + } + public static LoggerKey Log(string sessionKey, LogMessageType type, DateTime dateCreated, string target, string action, string message) + { + if (_isRun) + { + Guid key = Guid.NewGuid(); + _items.TryAdd(key, new LoggerItems + { + SessionKey = sessionKey, + Action = action, + DateCreated = dateCreated, + Duration = 0, + LogMessageType = type, + Message = message, + Target = target + }); + + return new LoggerKey { Key = key }; + } + return new LoggerKey { Key = Guid.Empty }; ; + } + + public static void Commit(LoggerKey key, DateTime? finishDate = null) + { + if (_items.ContainsKey(key.Key)) + { + LoggerItems item = _items.GetByKey(key.Key); + + double duration = ((finishDate ?? DateTime.UtcNow) - item.DateCreated).TotalMilliseconds; + //_connectionString.ExecuteScalar("[Logs].[Insert_Logger]", true, new List> + //{ + // new KeyValuePair("@LogMessageType", (int)item.LogMessageType), + // new KeyValuePair("@SessionKey", item.SessionKey), + // new KeyValuePair("@DateCreate", item.DateCreated), + // new KeyValuePair("@Target", item.Target), + // new KeyValuePair("@Action", item.Action), + // new KeyValuePair("@Message", item.Message), + // new KeyValuePair("@Duration", duration), + //}); + + _items.TryRemove(key.Key, out item!); + } + } + public static void Commit(params LoggerKey[] keys) + { + foreach (LoggerKey key in keys) + { + Commit(key); + } + } + + public static void Commit(string sessionKey, LogMessageType type, string target, string action, DateTime dateTime, Dictionary logs) + { + foreach (var log in logs) + { + //_connectionString.ExecuteScalar("[Logs].[Insert_Logger]", true, new List> + //{ + // new KeyValuePair("@LogMessageType", (int)type), + // new KeyValuePair("@SessionKey", sessionKey), + // new KeyValuePair("@DateCreate", dateTime), + // new KeyValuePair("@Target", target), + // new KeyValuePair("@Action", action), + // new KeyValuePair("@Message", log.Key), + // new KeyValuePair("@Duration", log.Value), + //}); + } + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Log/OperationLogger.cs b/LibCommon/Kit.Core.Helpers/Log/OperationLogger.cs new file mode 100644 index 0000000..47b1381 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Log/OperationLogger.cs @@ -0,0 +1,92 @@ +namespace Kit.Helpers.Log +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + + public class OperationResult + { + public string Title { get; set; } + public int Level { get; set; } + public int Count { get; set; } + public TimeSpan Elapsed { get; set; } + public OperationResult() + { + Title = string.Empty; + } + } + + public interface IOperationLogger + { + void Log(string operation, int level, Action? action); + IEnumerable GetOperationResults(); + string GetOperationResultsAsText(); + } + + public class EmptyOperationLogger : IOperationLogger + { + public IEnumerable GetOperationResults() + { + return new List(); + } + public string GetOperationResultsAsText() + { + return ""; + } + public void Log(string operation, int level, Action? action) + { + if (action != null) action(); + } + } + + public class OperationLogger : IOperationLogger + { + private readonly IDictionary _operationResults; + + public OperationLogger() + { + _operationResults = new Dictionary(); + } + + public static IOperationLogger Empty { get { return new EmptyOperationLogger(); } } + + public void Log(string operation, int level, Action? action) + { + var stopwatch = new Stopwatch(); + + try + { + stopwatch.Start(); + if (action != null) action(); + } + finally + { + stopwatch.Stop(); + + OperationResult operationResult = _operationResults.GetByKey(operation); + if (operationResult == null) + { + operationResult = new OperationResult { Title = operation, Count = 0, Level = level, Elapsed = new TimeSpan() }; + _operationResults.Add(operation, operationResult); + } + operationResult.Count++; + operationResult.Elapsed += stopwatch.Elapsed; + } + } + + public IEnumerable GetOperationResults() + { + return _operationResults.Values; + } + + public string GetOperationResultsAsText() + { + string result = ""; + foreach (var operationResult in _operationResults) + { + result += operationResult.Value.Title + Environment.NewLine; + } + return result; + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Middleware/ExceptionHandlerMiddleware.cs b/LibCommon/Kit.Core.Helpers/Middleware/ExceptionHandlerMiddleware.cs new file mode 100644 index 0000000..b783f0a --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Middleware/ExceptionHandlerMiddleware.cs @@ -0,0 +1,156 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace Kit.Helpers; + +public enum StackTraceModesJson +{ + None = 0, + Text = 1, + Html = 2 +} + +public enum StackTraceModesHtml +{ + None = 0, + Text = 1, + Html = 2, + + CommentText = 3, + CommentHtml = 4, +} + +public interface IConfigurationExceptionHandler +{ + StackTraceModesJson StackTraceModeJson { get; } + StackTraceModesHtml StackTraceModeHtml { get; } +} + +public class ConfigurationExceptionHandler : IConfigurationExceptionHandler +{ + public StackTraceModesJson StackTraceModeJson { get; private set; } + public StackTraceModesHtml StackTraceModeHtml { get; private set; } + + public ConfigurationExceptionHandler(Microsoft.Extensions.Configuration.IConfiguration configuration) + { + var helper = new ConfigurationHelper(configuration, "ExceptionHandler"); + StackTraceModeJson = helper.GetAppSettingValue("StackTraceModeJson", required: false, defaultValue: StackTraceModesJson.None.ToString()).ParseToEnum(); + StackTraceModeHtml = helper.GetAppSettingValue("StackTraceModeHtml", required: false, defaultValue: StackTraceModesHtml.None.ToString()).ParseToEnum(); + } +} + +public static class ExceptionHandlerMiddlewareExt +{ + public static IServiceCollection InitExceptionHandlerMiddleware(this IServiceCollection builder, IConfigurationExceptionHandler configuration) + { + ExceptionHandlerMiddleware.Init(configuration); + return builder; + } +} + +public class ExceptionHandlerMiddleware +{ + private static IConfigurationExceptionHandler? _exceptionHandler; + private static StackTraceModesJson _stackTraceModeJson => _exceptionHandler?.StackTraceModeJson ?? StackTraceModesJson.None; + private static StackTraceModesHtml _stackTraceModeHtml => _exceptionHandler?.StackTraceModeHtml ?? StackTraceModesHtml.None; + + private readonly RequestDelegate _next; + + public static void Init(IConfigurationExceptionHandler exceptionHandler) + { + _exceptionHandler = exceptionHandler; + } + + public ExceptionHandlerMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task Invoke(HttpContext context) + { + try + { + await _next(context); + } + catch (Exception ex) + { + int statusCode = 0; + string errorMessage = string.Empty; + string errorStackTrace = string.Empty; + string? redirectUrl = null; + bool forceJson = false; + + switch (ex) + { + case AuthenticationException exception: + statusCode = StatusCodes.Status401Unauthorized; + errorMessage = exception.Message; + redirectUrl = exception.RedirectUrl; + forceJson = true; + break; + + case AuthorizationException exception: + statusCode = StatusCodes.Status403Forbidden; + errorMessage = exception.Message; + redirectUrl = exception.RedirectUrl; + forceJson = true; + break; + + case InvalidOperationException exception: + statusCode = StatusCodes.Status409Conflict; + errorMessage = exception.Message; + break; + default: + statusCode = StatusCodes.Status500InternalServerError; + errorMessage = "На сервере произошла неизвестная ошибка" + Environment.NewLine + ex.Message; + break; + } + + context.Response.StatusCode = statusCode; + + if (forceJson || context.NeedResponseJsonResult(ex)) + { + switch (_stackTraceModeJson) + { + case StackTraceModesJson.Text: + errorStackTrace = ex?.GetInfoAsPlainText() ?? string.Empty; + break; + case StackTraceModesJson.Html: + errorStackTrace = ex?.GetInfoAsHtml() ?? string.Empty; + break; + } + + string json = new JsonResultError + { + ErrorMessage = errorMessage, + ErrorStackTrace = errorStackTrace, + RedirectUrl = redirectUrl + }.JsonSerialize(enableCyrillic: true, propertyNamingPolicy: System.Text.Json.JsonNamingPolicy.CamelCase); + context.Response.ContentType = "application/json; charset=utf-8"; + await context.Response.WriteAsync(json, System.Text.Encoding.UTF8); + } + else + { + switch (_stackTraceModeHtml) + { + case StackTraceModesHtml.Text: + errorStackTrace = ex.GetInfoAsPlainText() ?? string.Empty; + break; + case StackTraceModesHtml.Html: + errorStackTrace = ex.GetInfoAsHtml() ?? string.Empty; + break; + case StackTraceModesHtml.CommentText: + errorStackTrace = ""; + break; + case StackTraceModesHtml.CommentHtml: + errorStackTrace = ""; + break; + } + + errorMessage += Environment.NewLine + errorStackTrace; + context.Response.ContentType = "text/html; charset=utf-8"; + await context.Response.WriteAsync(errorMessage, System.Text.Encoding.UTF8); + } + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Mvc/TagHelpers/AttributeMatcher.cs b/LibCommon/Kit.Core.Helpers/Mvc/TagHelpers/AttributeMatcher.cs new file mode 100644 index 0000000..0388653 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Mvc/TagHelpers/AttributeMatcher.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Microsoft.AspNetCore.Mvc.TagHelpers; + +/// +/// Methods for determining how an should run based on the attributes that were specified. +/// +internal static class AttributeMatcher +{ + /// + /// Determines the most effective mode a can run in based on which modes have + /// all their required attributes present. + /// + /// The type representing the 's modes. + /// The . + /// The modes and their required attributes. + /// A comparer delegate. + /// The resulting most effective mode. + /// true if a mode was determined, otherwise false. + public static bool TryDetermineMode( + TagHelperContext context, + ModeAttributes[] modeInfos, + Func compare, + out TMode result) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(modeInfos); + ArgumentNullException.ThrowIfNull(compare); + + var foundResult = false; + result = default; + + // Perf: Avoid allocating enumerator + var allAttributes = context.AllAttributes; + // Read interface .Count once rather than per iteration + var allAttributesCount = allAttributes.Count; + foreach (var modeInfo in modeInfos) + { + var requiredAttributes = modeInfo.Attributes; + // If there are fewer attributes present than required, one or more of them must be missing. + if (allAttributesCount >= requiredAttributes.Length && + !HasMissingAttributes(allAttributes, requiredAttributes) && + compare(result, modeInfo.Mode) <= 0) + { + foundResult = true; + result = modeInfo.Mode; + } + } + + return foundResult; + } + + private static bool HasMissingAttributes(ReadOnlyTagHelperAttributeList allAttributes, string[] requiredAttributes) + { + // Check for all attribute values + // Perf: Avoid allocating enumerator + for (var i = 0; i < requiredAttributes.Length; i++) + { + if (!allAttributes.TryGetAttribute(requiredAttributes[i], out var attribute)) + { + // Missing attribute. + return true; + } + + if (attribute.Value is string valueAsString && string.IsNullOrEmpty(valueAsString)) + { + // Treat attributes with empty values as missing. + return true; + } + } + + return false; + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Mvc/TagHelpers/BootstrapTagHelper.cs b/LibCommon/Kit.Core.Helpers/Mvc/TagHelpers/BootstrapTagHelper.cs new file mode 100644 index 0000000..2efa82d --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Mvc/TagHelpers/BootstrapTagHelper.cs @@ -0,0 +1,68 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Kit.Helpers.Mvc.TagHelpers +{ + [HtmlTargetElement(Attributes = "asp-viewmodel", TagStructure = TagStructure.NormalOrSelfClosing)] + public class BootstrapTagHelper : TagHelper + { + [HtmlAttributeName("asp-viewmodel")] + public BaseViewModel Model { get; set; } + + public override void Process(TagHelperContext context, TagHelperOutput output) + { + base.Process(context, output); + if (Model == null || Model.NeedLoadBootstrap || output.Attributes.IsNullOrEmpty()) + { + return; + } + + IEnumerable> keyValuePairs = output.Attributes!.Select(x => new KeyValuePair(x.Name, x.Value?.ToString() ?? string.Empty)); + var keyValuePairsNew = new List>(); + keyValuePairs.ForEach(x => + { + string key = x.Key; + string value = x.Value?.ToString() ?? string.Empty; + + if (x.Key.ToLowerEquals("class")) + { + value = ProcessClassAttributeValue(value); + } + else if (x.Key.ToLowerStartsWith("data-bs-")) + { + key = ProcessDataBsAttributeName(x.Key); + } + + keyValuePairsNew.Add(new KeyValuePair(key, value)); + }); + + output.Attributes.Clear(); + keyValuePairsNew.ForEach(x => output.Attributes.SetAttribute(x.Key, x.Value)); + } + + private string ProcessClassAttributeValue(string value) + { + return (value ?? string.Empty).Split(' ').ToList().Select(x => + { + if (Model.NeedDowngradeBootstrap) + { + if (x.StartsWith("ms-")) return "ml-" + x.Substring(3); + if (x.StartsWith("me-")) return "mr-" + x.Substring(3); + if (x.StartsWith("ps-")) return "pl-" + x.Substring(3); + if (x.StartsWith("pe-")) return "pr-" + x.Substring(3); + } + + if (Model.NeedDowngradeFaIcons) + { + if (x.ToLowerEquals("fa-solid")) return "fa"; + } + + return x; + }).Join(" "); + } + private string ProcessDataBsAttributeName(string value) + { + return "data-" + value.Substring("data-bs-".Length); + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Mvc/TagHelpers/FileTagHelper.cs b/LibCommon/Kit.Core.Helpers/Mvc/TagHelpers/FileTagHelper.cs new file mode 100644 index 0000000..c2173a1 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Mvc/TagHelpers/FileTagHelper.cs @@ -0,0 +1,254 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Razor.Infrastructure; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Mvc.TagHelpers; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection; +using System.Text.Encodings.Web; + +namespace Kit.Helpers.Mvc.TagHelpers +{ + public class FileTagHelperContext + { + public enum LoadEntityType + { + Script = 1, + Stylesheet = 2, + } + + public enum PriorityChangeMode + { + Same = 1, + After = 2, + } + + public class File + { + public File(string url, LoadEntityType type, uint priority) + { + Url = url; + Type = type; + Priority = priority; + Loaded = false; + } + + public string Url { get; } + public LoadEntityType Type { get; } + public uint Priority { get; } + public bool Loaded { get; } + } + + public FileTagHelperContext(HttpContext? httpContext, bool needDynamicLoad, string allFilesLoadedEventName) + { + HttpContext = httpContext; + IsDynamicLoad = needDynamicLoad; + + EventNameAllFilesLoaded = allFilesLoadedEventName; + + Files = new List(); + + _priority = 0; + } + + public HttpContext? HttpContext { get; } + public bool IsDynamicLoad { get; } + + public string EventNameAllFilesLoaded { get; } + + public bool NeedPrependDomain { get; init; } + public bool NeedAppendVersion { get; init; } + + public List Files { get; } + + + private uint _priority; + + public void SetPriorityAfter() + { + _priority++; + } + + public void AddFile(string url, LoadEntityType entityType, PriorityChangeMode mode) + { + if (Files.Any(x => x.Url == url)) return; + if (mode == PriorityChangeMode.After) SetPriorityAfter(); + + Files.Add(new File(url, entityType, _priority)); + } + } + + [HtmlTargetElement(TagName, Attributes = $"[{PathExactAttributeName}^='~/']", TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement(TagName, Attributes = TypeAttributeName, TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement(TagName, Attributes = ContextAttributeName, TagStructure = TagStructure.WithoutEndTag)] + public class FileTagHelper : Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper + { + private const string TagName = "file"; + private const string TypeAttributeName = "type"; + private const string PriorityModeAttributeName = "priority-mode"; + + private const string ContextAttributeName = "asp-context"; + private const string PathExactAttributeName = "asp-path-exact"; + private const string PathIncludeAttributeName = "asp-path-include"; + private const string PathExcludeAttributeName = "asp-path-exclude"; + +#pragma warning disable + /// Creates a new . + public FileTagHelper(IWebHostEnvironment hostingEnvironment, TagHelperMemoryCacheProvider cacheProvider, IFileVersionProvider fileVersionProvider, HtmlEncoder htmlEncoder, JavaScriptEncoder javaScriptEncoder, IUrlHelperFactory urlHelperFactory) : base(urlHelperFactory, htmlEncoder) + { + HostingEnvironment = hostingEnvironment; + Cache = cacheProvider.Cache; + FileVersionProvider = fileVersionProvider; + } +#pragma warning enable + + /// + public override int Order => -1000; + + + [HtmlAttributeName(ContextAttributeName)] + public FileTagHelperContext Context { get; set; } + + [HtmlAttributeName(PathExactAttributeName)] + public string Path { get; set; } + + [HtmlAttributeName(TypeAttributeName)] + public FileTagHelperContext.LoadEntityType Type { get; set; } + + [HtmlAttributeName(PriorityModeAttributeName)] + public FileTagHelperContext.PriorityChangeMode PriorityChangeMode { get; set; } = FileTagHelperContext.PriorityChangeMode.Same; + + + /// A comma separated list of globbed file patterns to load. The glob patterns are assessed relative to the application's 'webroot' setting. + [HtmlAttributeName(PathIncludeAttributeName)] + public string PathInclude { get; set; } + + /// A comma separated list of globbed file patterns to exclude from loading. The glob patterns are assessed relative to the application's 'webroot' setting. Must be used in conjunction with . + [HtmlAttributeName(PathExcludeAttributeName)] + public string PathExclude { get; set; } + + /// Gets the for the application. + protected internal IWebHostEnvironment HostingEnvironment { get; } + + /// Gets the used to store globbed urls. + protected internal IMemoryCache Cache { get; } + + /// Gets the used to populate included and excluded urls. + // Internal for ease of use when testing. + protected internal GlobbingUrlBuilder GlobbingUrlBuilder { get; set; } + + internal IFileVersionProvider FileVersionProvider { get; private set; } + + /// + public override void Process(TagHelperContext context, TagHelperOutput output) + { + if (context == null) throw new ArgumentNullException(nameof(context)); + if (output == null) throw new ArgumentNullException(nameof(output)); + + var builder = output.PostElement; + builder.Clear(); + + output.TagName = null; + output.Content.SetContent(string.Empty); + + IEnumerable urls = SelectUrls(); + + urls = urls.Where(url => + { + if (string.IsNullOrWhiteSpace(url)) return false; + + string mimeType = url.GetMimeType(); + + switch (Type) + { + case FileTagHelperContext.LoadEntityType.Stylesheet: + return url.GetMimeType() == ".css".GetMimeType(); + case FileTagHelperContext.LoadEntityType.Script: + return url.GetMimeType() == ".js".GetMimeType(); + default: + return false; + } + }); + + urls.ForEach((url, index) => + { + string urlFull = GetVersionedUrl(url); + FileTagHelperContext.PriorityChangeMode mode = FileTagHelperContext.PriorityChangeMode.Same; + + if (index == 0) + { + mode = PriorityChangeMode; + } + + Context.AddFile(urlFull, Type, mode); + }); + } + + private GlobbingUrlBuilder EnsureGlobbingUrlBuilder() + { + if (GlobbingUrlBuilder == null) + { + GlobbingUrlBuilder = new GlobbingUrlBuilder(HostingEnvironment.WebRootFileProvider, Cache, ViewContext.HttpContext.Request.PathBase); + } + + return GlobbingUrlBuilder; + } + + private IEnumerable SelectUrls() + { + var result = new List(); + if (Path != null) + { + result.Add(Path); + } + + if (string.IsNullOrWhiteSpace(PathInclude) == false) + { + IReadOnlyList? urls = EnsureGlobbingUrlBuilder().BuildUrlList(null, PathInclude, PathExclude); + if (urls.IsNullOrEmpty() == false) + { + result.AddRange(urls); + } + } + + return result; + } + + + private IFileVersionProvider EnsureFileVersionProvider() + { + if (FileVersionProvider == null) + { + FileVersionProvider = ViewContext.HttpContext.RequestServices.GetRequiredService(); + } + + return FileVersionProvider; + } + + private string GetVersionedUrl(string url) + { + if (Context.NeedAppendVersion == true) + { + url = EnsureFileVersionProvider().AddFileVersionToPath(ViewContext.HttpContext.Request.PathBase, url); + } + + if (Context.NeedPrependDomain && Context.HttpContext != null) + { + string rootUrl = Context.HttpContext.GetRootUrl(); + // Заменяем символ "~" на домен + if (url.StartsWith("~")) + { + url = rootUrl + url.Substring(1); + } + else if (url.StartsWith("/")) + { + url = rootUrl + url; + } + } + + return url; + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Mvc/TagHelpers/LinkTagHelper.cs b/LibCommon/Kit.Core.Helpers/Mvc/TagHelpers/LinkTagHelper.cs new file mode 100644 index 0000000..7fdc553 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Mvc/TagHelpers/LinkTagHelper.cs @@ -0,0 +1,62 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Razor.Infrastructure; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Mvc.TagHelpers; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; +using System.Text.Encodings.Web; + +namespace Kit.Helpers.Mvc.TagHelpers +{ + [HtmlTargetElement("link", Attributes = "asp-context", TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("link", Attributes = "asp-prepend-domain", TagStructure = TagStructure.WithoutEndTag)] + public class LinkTagHelper : Microsoft.AspNetCore.Mvc.TagHelpers.LinkTagHelper + { + public LinkTagHelper(IWebHostEnvironment hostingEnvironment, TagHelperMemoryCacheProvider cacheProvider, IFileVersionProvider fileVersionProvider, HtmlEncoder htmlEncoder, JavaScriptEncoder javaScriptEncoder, IUrlHelperFactory urlHelperFactory) : base(hostingEnvironment, cacheProvider, fileVersionProvider, htmlEncoder, javaScriptEncoder, urlHelperFactory) + { + } + + [HtmlAttributeName("asp-context")] + public HttpContext? HttpContext { get; set; } + + [HtmlAttributeName("asp-prepend-domain")] + public bool NeedPrependDomain { get; set; } + + public override void Process(TagHelperContext context, TagHelperOutput output) + { + base.Process(context, output); + if (NeedPrependDomain && HttpContext != null) + { + string rootUrl = HttpContext.GetRootUrl(); + + TagHelperAttribute attributeHref = output.Attributes["href"]; + if (attributeHref != null) + { + var href = output.Attributes["href"].Value.ToString(); + + // Заменяем символ "~" на ваш домен + if (href.StartsWith("~")) + { + href = rootUrl + href.Substring(1); + } + else if (href.StartsWith("/")) + { + href = rootUrl + href; + } + + output.Attributes.SetAttribute("href", href); + } + else if (output.PostElement != null && output.PostElement.IsEmptyOrWhiteSpace == false) + { + string str = output.PostElement.GetContent(); + str = str.Replace("href=\"/", $"href=\"{rootUrl}/").Replace("href=\"~/", $"href=\"{rootUrl}/"); + output.PostElement.SetHtmlContent(str); + + ; + } + } + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Mvc/TagHelpers/ModeAttributesOfT.cs b/LibCommon/Kit.Core.Helpers/Mvc/TagHelpers/ModeAttributesOfT.cs new file mode 100644 index 0000000..0fd91ed --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Mvc/TagHelpers/ModeAttributesOfT.cs @@ -0,0 +1,29 @@ +namespace Microsoft.AspNetCore.Mvc.TagHelpers; + +/// +/// A mapping of a mode to its required attributes. +/// +/// The type representing the 's mode. +internal sealed class ModeAttributes +{ + /// + /// Initializes a new instance of . + /// + /// The 's mode. + /// The names of attributes required for this mode. + public ModeAttributes(TMode mode, string[] attributes) + { + Mode = mode; + Attributes = attributes; + } + + /// + /// Gets the 's mode. + /// + public TMode Mode { get; } + + /// + /// Gets the names of attributes required for this mode. + /// + public string[] Attributes { get; } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Mvc/TagHelpers/ScriptTagHelper.cs b/LibCommon/Kit.Core.Helpers/Mvc/TagHelpers/ScriptTagHelper.cs new file mode 100644 index 0000000..8242e6e --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Mvc/TagHelpers/ScriptTagHelper.cs @@ -0,0 +1,120 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Razor.Infrastructure; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; +using System.Text.Encodings.Web; + +namespace Kit.Helpers.Mvc.TagHelpers +{ + //public class ScriptIdCollection : List + //{ + + //} + [HtmlTargetElement("script", Attributes = "asp-context")] + [HtmlTargetElement("script", Attributes = "asp-prepend-domain")] + [HtmlTargetElement("script", Attributes = "asp-await-collection")] + public class ScriptTagHelper : Microsoft.AspNetCore.Mvc.TagHelpers.ScriptTagHelper + { + public ScriptTagHelper(IWebHostEnvironment hostingEnvironment, TagHelperMemoryCacheProvider cacheProvider, IFileVersionProvider fileVersionProvider, HtmlEncoder htmlEncoder, JavaScriptEncoder javaScriptEncoder, IUrlHelperFactory urlHelperFactory) : base(hostingEnvironment, cacheProvider, fileVersionProvider, htmlEncoder, javaScriptEncoder, urlHelperFactory) + { + } + + [HtmlAttributeName("asp-context")] + public HttpContext? HttpContext { get; set; } + + [HtmlAttributeName("asp-prepend-domain")] + public bool NeedPrependDomain { get; set; } + + //[HtmlAttributeName("asp-await-collection")] + //public ScriptIdCollection? AwaitCollection { get; set; } + + public override void Process(TagHelperContext context, TagHelperOutput output) + { + base.Process(context, output); + if (NeedPrependDomain && HttpContext != null) + { + string rootUrl = HttpContext.GetRootUrl(); + + TagHelperAttribute attributeSrc = output.Attributes["src"]; + if (attributeSrc != null) + { + var src = output.Attributes["src"].Value.ToString(); + + // Заменяем символ "~" на ваш домен + if (src.StartsWith("~")) + { + src = rootUrl + src.Substring(1); + } + else if (src.StartsWith("/")) + { + src = rootUrl + src; + } + + output.Attributes.SetAttribute("src", src); + + //if (AwaitCollection != null) + //{ + // TagHelperAttribute attributeId = output.Attributes["id"]; + // string id = attributeId?.Value?.ToString() ?? "script".AppendToken(); + // output.Attributes.SetAttribute("id", id); + + // AwaitCollection.Add(id); + //} + } + else if (output.PostElement != null && output.PostElement.IsEmptyOrWhiteSpace == false) + { + string str = output.PostElement.GetContent(); + str = str.Replace("src=\"/", $"src=\"{rootUrl}/").Replace("src=\"~/", $"src=\"{rootUrl}/"); + output.PostElement.SetHtmlContent(str); + + ; + } + } + } + } + //[HtmlTargetElement("scriptawaiter")] + //public class ScriptAwaiterTagHelper : TagHelper + //{ + // public const string EventNameAllLoaded = "allScriptsLoaded"; + + // [HtmlAttributeName("asp-await-collection")] + // public ScriptIdCollection? AwaitCollection { get; set; } + + // public override void Init(TagHelperContext context) + // { + // base.Init(context); + // } + + // public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + // { + // output.TagName = "script"; + // output.TagMode = TagMode.StartTagAndEndTag; + + // output.Attributes.SetAttribute("class", "scriptawaiter"); + + // var stringBuilder = new StringBuilder(); + + // stringBuilder.AppendLine(""); + // stringBuilder.AppendLine("const awaitingScripts = " + AwaitCollection?.ToDictionary(x => x, x=> false).JsonSerialize(true) ?? "{}"); + // stringBuilder.AppendLine("console.log(awaitingScripts);"); + // stringBuilder.AppendLine("$('script[src]').on('load', function() {"); + // { + // stringBuilder.AppendLine("var itemId = this.id;"); + // stringBuilder.AppendLine("console.log('file loaded: ' + itemId);"); + // stringBuilder.AppendLine("if (Object.keys(awaitingScripts).indexOf(itemId) != -1) {"); + // stringBuilder.AppendLine("awaitingScripts[itemId] = true;"); + // stringBuilder.AppendLine("var allLoaded = true;"); + // stringBuilder.AppendLine("Object.keys(awaitingScripts).forEach(key => { if (awaitingScripts[key] == false) allLoaded = false; });"); + // stringBuilder.AppendLine("if (allLoaded) { window.dispatchEvent(new Event('" + EventNameAllLoaded + "')); };"); + // stringBuilder.AppendLine("}"); + // } + // stringBuilder.AppendLine("});"); + + // output.Content.SetHtmlContent(stringBuilder.ToString()); + + // return Task.CompletedTask; + // } + //} +} diff --git a/LibCommon/Kit.Core.Helpers/Repository/Converter/IConverter.cs b/LibCommon/Kit.Core.Helpers/Repository/Converter/IConverter.cs new file mode 100644 index 0000000..e4c4a26 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/Converter/IConverter.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Kit.Helpers.Repository.Converter +{ + public interface IConverter + { + TOutput Convert(TInput input); + } + public interface IEnumerableConverter : + IConverter, IEnumerable> { } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Repository/IContent.cs b/LibCommon/Kit.Core.Helpers/Repository/IContent.cs new file mode 100644 index 0000000..c85ea48 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/IContent.cs @@ -0,0 +1,63 @@ +using System.Xml; + +namespace Kit.Helpers.Repository +{ + public enum ContentTypes + { + Xml = 0, + Sqlite = 1, + } + + public interface IFileContent + { + ContentTypes Type { get; } + object Content { get; } + } + + public abstract class FileContent : IFileContent + { + public abstract ContentTypes Type { get; } +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + public object Content { get; set; } +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + } + + public class FileContentXml : FileContent + { + public override ContentTypes Type => ContentTypes.Xml; + public FileContentXml(XmlDocument xmlDocument) + { + Content = xmlDocument; + } + } + + public class FileContentSqlite : FileContent + { + public override ContentTypes Type => ContentTypes.Sqlite; + public IConnectionString ConnectionString => (IConnectionString)Content; + public FileContentSqlite(IConnectionString connectionString) + { + Check.IsTrue(connectionString.Type.Equals(ConnectionStringType.SQLite), $"value ({nameof(connectionString)}.{nameof(connectionString.Type)} = {connectionString.Type.ToString()}) is invalid - {ConnectionStringType.SQLite.ToString()} was expected"); + Content = connectionString; + } + } + + public interface IContent + { + public ContentTypes Type { get; } + IFileContent GetContentDataDefault(); + IFileContent? GetContentData(TContentKey key); + bool CreateContentData(TContentKey key, IFileContent content, bool skipIfExists = true); + void UpdateContentData(TContentKey key, IFileContent content); + int GetRowVersion(IFileContent document); + } + + public abstract class ContentBase + { + public abstract ContentTypes Type { get; } + public void CheckFileContent(IFileContent content) + { + Check.IsTrue(content.Type.Equals(Type), $"value ({nameof(content)}.{nameof(content.Type)} = {content.Type.ToString()}) is invalid - {Type.ToString()} was expected"); + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Repository/IDictionaryRepository.cs b/LibCommon/Kit.Core.Helpers/Repository/IDictionaryRepository.cs new file mode 100644 index 0000000..b61a4fb --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/IDictionaryRepository.cs @@ -0,0 +1,82 @@ +using Microsoft.Extensions.Caching.Memory; +using Kit.Helpers.Cache; +using Kit.Helpers.Repository.Xml; +using System; +using System.Collections.Generic; + +namespace Kit.Helpers.Repository +{ + public interface IDictionaryRepository + { + IDictionary Select(); + TEntity Get(TKey key); + void ClearCache(); + } + + + public abstract class DictionaryRepository : IDictionaryRepository + where TEntity : IPersistentKey + { + private string _cacheKey; + private ICacheProvider _cacheProvider; + public DictionaryRepository(ICacheProvider cacheProvider, string cacheKey) + { + _cacheProvider = cacheProvider; + _cacheKey = cacheKey; + } + + // лучше сделать абстрактным + protected abstract IEnumerable GetData(); + + public IDictionary Select() + { + IDictionary values; + if (_cacheKey.IsNullOrEmpty()) + { + throw new ArgumentNullException("Не задан ключ кеширования для репозитория: {0}".ApplyFormat(this.GetType().ToString())); + } + + if (_cacheProvider.Contains(_cacheKey)) + { + values = _cacheProvider.GetCacheData>(_cacheKey); + } + else + { + + IEnumerable data = this.GetData(); + values = new Dictionary(); + // добавить обработку дубликатов + foreach (var item in data) + { + TKey key = item.PersistentKey; + if (values.ContainsKey(key)) + { + throw new Exception("Элемент с ключом {0} уже добавлен в коллекцию".ApplyFormat(key)); + } + values.Add(item.PersistentKey, item); + } + + _cacheProvider.SetCacheData(_cacheKey, values, + new MemoryCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1), + Priority = CacheItemPriority.Normal + }); + } + return values; + } + public TEntity Get(TKey key) + { + IDictionary values = this.Select(); + if (values.ContainsKey(key)) + return values[key]; + else + return default(TEntity); + } + + public void ClearCache() + { + _cacheProvider.PurgeCacheItems(_cacheKey); + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Repository/ISelectContext.cs b/LibCommon/Kit.Core.Helpers/Repository/ISelectContext.cs new file mode 100644 index 0000000..8e0cc50 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/ISelectContext.cs @@ -0,0 +1,22 @@ +using Kit.Helpers.Log; + +namespace Kit.Helpers.Repository +{ + public interface ISelectContext + { + IDictionary Items { get; } + ICallStackLog CallStackLog { get; set; } + } + + public class SelectContext : ISelectContext + { + public SelectContext() + { + Items = new Dictionary(); + CallStackLog = Log.CallStackLog.Empty; + } + + public IDictionary Items { get; } + public ICallStackLog CallStackLog { get; set; } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Repository/Paging/IPageFilter.cs b/LibCommon/Kit.Core.Helpers/Repository/Paging/IPageFilter.cs new file mode 100644 index 0000000..3fb871b --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/Paging/IPageFilter.cs @@ -0,0 +1,9 @@ +using System.Linq; + +namespace Kit.Helpers.Repository.Paging +{ + public interface IPageFilter + { + IQueryable ChangePage(IQueryable query); + } +} diff --git a/LibCommon/Kit.Core.Helpers/Repository/Paging/ISqlPageFilter.cs b/LibCommon/Kit.Core.Helpers/Repository/Paging/ISqlPageFilter.cs new file mode 100644 index 0000000..127a321 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/Paging/ISqlPageFilter.cs @@ -0,0 +1,19 @@ +namespace Kit.Helpers.Repository.Paging +{ + + public interface ISqlPageFilter + { + int TotalRows { get; set; } + int? PageNo { get; set; } + int? PageSize { get; set; } + string Sort { get; set; } + } + + public class SqlPageFilter:ISqlPageFilter + { + public int TotalRows { get; set; } + public int? PageNo { get; set; } + public int? PageSize { get; set; } + public string Sort { get; set; } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Repository/Paging/PagingByPage.cs b/LibCommon/Kit.Core.Helpers/Repository/Paging/PagingByPage.cs new file mode 100644 index 0000000..1cdf6b1 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/Paging/PagingByPage.cs @@ -0,0 +1,72 @@ +using System.Linq; + +namespace Kit.Helpers.Repository.Paging +{ + /// + /// Фильтр с полями постраничной фильтрации. + /// Применяется, например в PagingController + /// + public class PagingByPage : IPageFilter + { + public int? Start { get; set; } + public int? Lenght { get; set; } + public string SortColumnName { get; set; } + public string SortDirection { get; set; } + public string Tabs { get; set; } + public string pageParameterId { get; set; } + + /// + /// Номер страницы. Нумерация с 1-цы + /// + public int? PageNo { get; set; } + public int? PageSize { get; set; } + public int? TotalRows { get; set; } + public int? CountPages { get; set; } + + /// + /// Есть значения для разбиения на страницы + /// + public bool HasSize + { + get + { + return + this.PageNo.HasValue && + (this.PageNo > 0) && + this.PageSize.HasValue && + (this.PageSize > 0); + } + } + + public string FormId + { + get + { + return _formId; + } + + set + { + _formId = value; + } + } + + public IQueryable ChangePage(IQueryable query) + { + if (query == null) return null; + + if (!(this.PageNo.HasValue && this.PageSize.HasValue)) return query; + + return query.Skip((this.PageNo.Value - 1) * this.PageSize.Value).Take(this.PageSize.Value); + } + + #region Fields --------------------------------------- + + /// + /// Для передачи в частичное представление Id формы, которая владеет просмотром страниц + /// + private string _formId = ""; + + #endregion + } +} diff --git a/LibCommon/Kit.Core.Helpers/Repository/Paging/PagingDatatable.cs b/LibCommon/Kit.Core.Helpers/Repository/Paging/PagingDatatable.cs new file mode 100644 index 0000000..b265ed1 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/Paging/PagingDatatable.cs @@ -0,0 +1,21 @@ +namespace Kit.Helpers.Repository.Paging +{ + public class PagingDatatable + { + //public int? Start { get; set; } + //public int? Length { get; set; } + public string SearchValue { get; set; } + //public string SortColumnName { get; set; } + //public string SortDirection { get; set; } + + + //public int TotalCount { get; set; } + //public int FilterCount { get; set; } + public int? Start { get; set; } + public int? Length { get; set; } + public int FilterRow { get; set; } + public int TotalRow { get; set; } + public string SortColumnName { get; set; } + public string SortDirection { get; set; } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Repository/SqlFileVersionRepository.cs b/LibCommon/Kit.Core.Helpers/Repository/SqlFileVersionRepository.cs new file mode 100644 index 0000000..9b8b9bc --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/SqlFileVersionRepository.cs @@ -0,0 +1,57 @@ +//using RiskProf.Files.Domain; +//using Kit.Helpers.SQLite; + +//namespace Kit.Helpers.DbEmbedded.Repository +//{ +// public class SqlFileVersionInfo +// { +// public SqlFileVersionInfo() +// { +// Type = string.Empty; +// Version = 0; +// } + +// public string Type { get; set; } +// public long Version { get; set; } +// } + +// public interface ISqlFileVersionRepository +// { +// SqlFileVersionInfo? Get(SQLiteFile file); +// void Set(SQLiteFile file, SqlFileVersionInfo info); +// } + +// public class SqlFileVersionRepository : ISqlFileVersionRepository +// { +// public SqlFileVersionRepository() +// { +// } + +// private const string _createIfNotExists = "create table if not exists fileVersion(type text, version integer); "; + +// public SqlFileVersionInfo? Get(SQLiteFile file) +// { +// return file.PrepareExecute(_createIfNotExists + "select type, version from fileVersion") +// .AsSqlText() +// .AddConverter((record, list) => +// { +// list.Add(new SqlFileVersionInfo +// { +// Type = record.Get("type"), +// Version = record.Get("version"), +// }); +// }) +// .ExecuteSelectMany() +// .SingleOrDefault(); +// } + +// public void Set(SQLiteFile file, SqlFileVersionInfo info) +// { +// file.PrepareExecute(_createIfNotExists + "delete from fileVersion; insert into fileVersion (type, version) values (@type, @version)") +// .AsSqlText() +// .AddParameter("type", info.Type) +// .AddParameter("version", info.Version) +// .ExecuteNonQuery(); +// } +// } +//} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Repository/Sqlite/SQLiteFileRepository.cs b/LibCommon/Kit.Core.Helpers/Repository/Sqlite/SQLiteFileRepository.cs new file mode 100644 index 0000000..4533c87 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/Sqlite/SQLiteFileRepository.cs @@ -0,0 +1,188 @@ +using Kit.Helpers.Service; + +namespace Kit.Helpers.Repository +{ + public abstract class SQLiteFileRepository : ContentBase, IContent + { + public override ContentTypes Type => ContentTypes.Sqlite; + + protected readonly ILockService _lockService; + protected readonly ISQLiteVersionService _sqLiteVersionService; + + /// Ключ типа файла для версионности + protected readonly string _fileTypeKey; + + /// + /// + /// + /// Ключ типа файла для версионности + public SQLiteFileRepository( + ISQLiteVersionService sqLiteVersionService, + ISQLiteGlobalVarRepository sqLiteGlobalVarRepository, + string fileTypeKey + ) + { + _lockService = new LockService(); + _sqLiteVersionService = sqLiteVersionService; + _fileTypeKey = fileTypeKey; + } + + protected abstract Stream? ReadFile(TContentKey key); + protected abstract bool ExistsFile(TContentKey key); + protected abstract void SaveFile(TContentKey key, Stream stream); + + private void CheckFileContentType(IFileContent fileContent) + { + Check.IsTrue(fileContent is FileContentSqlite, $"{nameof(fileContent)} is not {nameof(FileContentSqlite)}"); + } + private FileContentSqlite CreateTempFile() => new FileContentSqlite(new ConnectionString + { + Type = ConnectionStringType.SQLite, + Value = Path.GetTempFileName() + }); + + public IFileContent GetContentData(TContentKey key) + { + return _lockService.Lock(key, () => + { + bool isUpdated = false; + + var fileContent = CreateTempFile(); + try + { + using (Stream? srcStream = ReadFile(key)) + { + Check.IsNotNull(srcStream, $"{nameof(srcStream)} is null"); + using (FileStream fileStream = File.OpenWrite(fileContent.ConnectionString.Value)) + { + srcStream.CopyTo(fileStream); + } + } + + isUpdated = _sqLiteVersionService.UpdateByTypes(fileContent); + if (isUpdated) + { + ExecSave(key, fileContent); + } + + return fileContent; + } + finally + { + File.Delete(fileContent.ConnectionString.Value); + } + }); + } + + public IFileContent GetContentDataDefault() + { + FileContentSqlite fileContent = CreateTempFile(); + + _sqLiteVersionService.AppendType(fileContent, FileTypeKeys.SQLiteAll); + _sqLiteVersionService.AppendType(fileContent, _fileTypeKey); + _sqLiteVersionService.UpdateByTypes(fileContent); + + return fileContent; + } + + public bool CreateContentData(TContentKey key, IFileContent content, bool skipIfExists = true) + { + CheckFileContentType(content); + return _lockService.Lock(key, () => + { + if (ExistsFile(key)) + { + if (skipIfExists) return false; + Check.IsNotTrue(true, "Документ уже существует"); + } + + ExecSave(key, (FileContentSqlite)content); + + return true; + }); + } + + public void UpdateContentData(TContentKey key, IFileContent file) + { + Check.IsTrue(file is FileContentSqlite, $"{nameof(file)} is not {nameof(FileContentSqlite)}"); + _lockService.Lock(key, () => + { + ExecSave(key, (FileContentSqlite)file); + }); + } + + private void ExecSave(TContentKey key, FileContentSqlite fileContent) + { + using (Stream stream = File.OpenRead(fileContent.ConnectionString.Value)) + { + SaveFile(key, stream); + } + } + + public int GetRowVersion(IFileContent fileContent) + { + CheckFileContentType(fileContent); + return _sqLiteVersionService.GetRowVersion((FileContentSqlite)fileContent); + } + } + + public class SQLiteFileFullPathRepository : SQLiteFileRepository + { + public SQLiteFileFullPathRepository(ISQLiteVersionService sqLiteVersionService, ISQLiteGlobalVarRepository sqLiteGlobalVarRepository, string fileTypeKey) : base(sqLiteVersionService, sqLiteGlobalVarRepository, fileTypeKey) { } + + protected override bool ExistsFile(string fullPath) + { + return File.Exists(fullPath); + } + + protected override Stream? ReadFile(string fullPath) + { + return File.Open(fullPath, FileMode.OpenOrCreate); + } + + protected override void SaveFile(string fullPath, Stream stream) + { + using (Stream streamFile = File.Open(fullPath, FileMode.Create)) + { + long posOld = 0; + + if (stream.CanSeek) + posOld = stream.Position; + + stream.CopyTo(streamFile); + + if (stream.CanSeek) + stream.Position = posOld; + } + } + + public void Init(string fullPath) + { + if (File.Exists(fullPath)) + GetContentData(fullPath); + else + CreateContentData(fullPath, GetContentDataDefault(), true); + } + } + + public interface ISQLiteFileFullPathFactory + { + SQLiteFileFullPathRepository Create(string fileTypeKey); + } + + public class SQLiteFileFullPathFactory : ISQLiteFileFullPathFactory + { + + private readonly ISQLiteVersionService _sqLiteVersionService; + private readonly ISQLiteGlobalVarRepository _sqLiteGlobalVarRepository; + public SQLiteFileFullPathFactory(ISQLiteVersionService sqLiteVersionService, ISQLiteGlobalVarRepository sqLiteGlobalVarRepository) + { + _sqLiteVersionService = sqLiteVersionService; + _sqLiteGlobalVarRepository = sqLiteGlobalVarRepository; + } + + public SQLiteFileFullPathRepository Create(string fileTypeKey) => new SQLiteFileFullPathRepository(_sqLiteVersionService, _sqLiteGlobalVarRepository, fileTypeKey); + + + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Repository/Sqlite/SQLiteGlobalVarRepository.cs b/LibCommon/Kit.Core.Helpers/Repository/Sqlite/SQLiteGlobalVarRepository.cs new file mode 100644 index 0000000..ae06593 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/Sqlite/SQLiteGlobalVarRepository.cs @@ -0,0 +1,48 @@ +namespace Kit.Helpers.Repository +{ + public interface ISQLiteGlobalVarRepository + { + string? Get(FileContentSqlite fileContentSqlite, string key); + void Set(FileContentSqlite fileContentSqlite, string key, string? value); + } + + public class SQLiteGlobalVarRepository : ISQLiteGlobalVarRepository + { + public SQLiteGlobalVarRepository() { } + + private const string _createIfNotExists = "create table if not exists global_var (key text not null primary key, value text not null); "; + private const string _getByKey = "select main.value from global_var main where main.key = :key; "; + private const string _deleteByKey = "delete from global_var where main.key = :key; "; + private const string _setByKey = "insert into global_var (key, value) values (:key, :value); "; + + public string? Get(FileContentSqlite fileContentSqlite, string key) + { + return fileContentSqlite.ConnectionString.PrepareExecute(_createIfNotExists + _getByKey) + .AsSqlText() + .WithStrictSyntax() + .AddParameter("key", key) + .ExecuteScalar(); + } + + + public void Set(FileContentSqlite fileContentSqlite, string key, string? value) + { + string request = _createIfNotExists + _deleteByKey; + if (string.IsNullOrWhiteSpace(value) == false) + { + request += _setByKey; + } + else + { + value = null; + } + + fileContentSqlite.ConnectionString.PrepareExecute(request) + .AsSqlText() + .WithStrictSyntax() + .AddParameter("key", key) + .AddParameterIfNotNull("value", value) + .ExecuteNonQuery(); + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Repository/Sqlite/SQLiteVersionRepository.cs b/LibCommon/Kit.Core.Helpers/Repository/Sqlite/SQLiteVersionRepository.cs new file mode 100644 index 0000000..f96d1d8 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/Sqlite/SQLiteVersionRepository.cs @@ -0,0 +1,52 @@ +namespace Kit.Helpers.Repository +{ + public class SQLiteFileVersionInfo + { + public SQLiteFileVersionInfo() + { + Type = string.Empty; + Version = 0; + } + + public string Type { get; set; } + public long Version { get; set; } + } + + public interface ISQLiteVersionRepository + { + IEnumerable Select(FileContentSqlite fileContentSqlite); + void Set(FileContentSqlite fileContentSqlite, SQLiteFileVersionInfo info); + } + + public class SQLiteVersionRepository : ISQLiteVersionRepository + { + public SQLiteVersionRepository() { } + + private const string _createIfNotExists = "create table if not exists file_version (type text not null primary key, version integer not null); "; + private const string _select = "select type, version from file_version; "; + private const string _setByType = "delete from file_version where type = :type; insert into file_version (type, version) values (:type, :version); "; + + public IEnumerable Select(FileContentSqlite fileContentSqlite) + { + return fileContentSqlite.ConnectionString.PrepareExecute(_createIfNotExists + _select) + .AsSqlText() + .WithStrictSyntax() + .AddConverter((record, list) => list.Add(new SQLiteFileVersionInfo + { + Type = record.Get("type"), + Version = record.Get("version"), + })) + .ExecuteSelectMany(); + } + + public void Set(FileContentSqlite fileContentSqlite, SQLiteFileVersionInfo info) + { + fileContentSqlite.ConnectionString.PrepareExecute(_createIfNotExists + _setByType) + .AsSqlText() + .WithStrictSyntax() + .AddParameter("type", info.Type) + .AddParameter("version", info.Version) + .ExecuteNonQuery(); + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Repository/Xml/EntityXmlPersistentComplexRepository.cs b/LibCommon/Kit.Core.Helpers/Repository/Xml/EntityXmlPersistentComplexRepository.cs new file mode 100644 index 0000000..56006af --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/Xml/EntityXmlPersistentComplexRepository.cs @@ -0,0 +1,162 @@ +using Kit.Helpers; +using Kit.Helpers.Repository.Converter; +using Kit.Helpers.Repository.Xml.Exceptions; +using System; +using System.Linq; +using System.Xml; + +namespace Kit.Helpers.Repository.Xml +{ + public abstract class EntityXmlPersistentComplexRepository : + EntityXmlSelectComplexRepository, + IPersistentXml2Repository + where TEntity : XmlComplexClass + where TFilter : IXmlComplexFilter + { + protected string _elementsAfterPostion; + + public EntityXmlPersistentComplexRepository(string elementName) : base(elementName) + { + } + + public EntityXmlPersistentComplexRepository(string elementName, IConverter converter) : base(elementName, converter) + { + } + + public EntityXmlPersistentComplexRepository(string rootElementName, string path, string elementName) : base(rootElementName, path, elementName) + { + } + + public EntityXmlPersistentComplexRepository(string path, string elementName) : base(path, elementName) + { + } + + public EntityXmlPersistentComplexRepository(string path, string elementName, IConverter converter) : base(path, elementName, converter) + { + } + + public EntityXmlPersistentComplexRepository(string rootElementName, string path, string elementName, IConverter converter) : base(rootElementName, path, elementName, converter) + { + } + + public EntityXmlPersistentComplexRepository(string elementsAfterPostion, string rootElementName, string path, string elementName, IConverter converter) : base(rootElementName, path, elementName, converter) + { + _elementsAfterPostion = elementsAfterPostion; + } + + protected virtual XmlNode GetElementAfterInsert(XmlNode root, XmlNode persistent = null) + { + if (!string.IsNullOrEmpty(_elementsAfterPostion)) + { + var elements = root.SelectNodes(_elementsAfterPostion); + return elements.Cast().LastOrDefault(); + } + return null; + } + + + protected abstract IEnumerable SetPersistentFromEntity(XmlDocument db, XmlNode persistent, TEntity entity); + + protected virtual bool OnInserting(XmlDocument db, XmlNode persistent, TEntity entity) { return true; } + protected virtual bool OnInserted(XmlDocument db, XmlNode persistent, TEntity entity) { return true; } + + protected virtual bool OnUpdating(XmlDocument db, XmlNode persistent, TEntity entity) { return true; } + protected virtual bool OnUpdated(XmlDocument db, XmlNode persistent, TEntity entity, IEnumerable itemLogs) + { + if (itemLogs.IsNullOrEmpty() == false) + { + ItemLogEventManager.Invoke(entity.ContentId, itemLogs); + } + + return true; + } + + + public virtual void Delete(XmlDocument db, TKey key) + { + var persistent = SelectNodesByKey(db, key).Cast().AsQueryable().FirstOrDefault(); + persistent.ParentNode.RemoveChild(persistent); + } + + protected override sealed XmlNodeList SelectNodesByKey(XmlDocument db, TKey key) + { + return base.SelectNodesByKey(db, key); + } + + public virtual TEntity Get(XmlDocument db, TKey key) + { + return Get(db, key, default); + } + public virtual TEntity Get(XmlDocument db, TKey key, TFilter filter) + { + XmlNodeList list = SelectNodesByKey(db, key); + return this.FillResult(db, list.Cast().AsQueryable().ToList().Select(x => this.FillXmlClass(this.ConvertToEntity(x, filter), x, filter)), filter).SingleOrDefault(); + } + + public virtual bool Exists(XmlDocument db, TKey key) + { + XmlNodeList list = SelectNodesByKey(db, key); + return list.Count > 0; + } + + protected virtual System.Linq.Expressions.Expression> GetByKeyExpression(TKey key) + { + return x => x.Attributes["guid"].InnerText == key.ToString(); + } + + public IEnumerable Update(XmlDocument db, TEntity entity) + { + var persistent = SelectNodesByKey(db, entity.PersistentKey).Cast().AsQueryable().FirstOrDefault(); + IEnumerable? itemLogs = null; + + if (this.OnUpdating(db, persistent, entity)) + { + //int row = persistent.GetValueAttributeInt32("rowVersion"); + int row = entity.RowVersion; + if (row != entity.RowVersion) throw new RowVersionException("Данные были изменены версии записи не совпадают"); + + itemLogs = this.SetPersistentFromEntity(db, persistent, entity); + persistent.SetAttributeValue("rowVersion", (++row).ToString()); + this.OnUpdated(db, persistent, entity, itemLogs); + } + return itemLogs ?? Array.Empty(); + } + + public TKey Insert(XmlDocument db, TEntity entity) + { + XmlNode root = GetRootNode(db, entity.RootId); + XmlNode parent = GetParentNode(root); + XmlNode persistent = parent.OwnerDocument.CreateNode(XmlNodeType.Element, _elementName, ""); + this.SetPersistentFromEntity(parent.OwnerDocument, persistent, entity); + + if (this.OnInserting(db, persistent, entity)) + { + var after = GetElementAfterInsert(parent, persistent); + if (after == null) parent.InsertBefore(persistent, parent.FirstChild); + else + { + parent.InsertAfter(persistent, after); + } + this.OnInserted(db, persistent, entity); + } + + return entity.PersistentKey; + } + + public TKey InsertToNode(XmlNode db, TEntity entity) + { + XmlNode parent = GetParentNode(db); + XmlNode persistent = parent.OwnerDocument.CreateNode(XmlNodeType.Element, _elementName, ""); + this.SetPersistentFromEntity(parent.OwnerDocument, persistent, entity); + + var after = GetElementAfterInsert(parent, persistent); + if (after == null) parent.InsertBefore(persistent, parent.FirstChild); + else + { + parent.InsertAfter(persistent, after); + } + + return entity.PersistentKey; + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Repository/Xml/EntityXmlPersistentComplexRepositoryWithCache.cs b/LibCommon/Kit.Core.Helpers/Repository/Xml/EntityXmlPersistentComplexRepositoryWithCache.cs new file mode 100644 index 0000000..cf87309 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/Xml/EntityXmlPersistentComplexRepositoryWithCache.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Xml; + +namespace Kit.Helpers.Repository.Xml +{ + public abstract class EntityXmlPersistentComplexRepositoryWithCache : EntityXmlPersistentComplexRepository + where TEntity : XmlComplexClass + where TFilter : IXmlComplexFilter + { + private string _cacheKey; + protected bool _cacheOn; + + private void CacheInit() + { + _cacheKey = this.GetType().FullName; + _cacheOn = true; + } + + public EntityXmlPersistentComplexRepositoryWithCache(string elementName) : base(elementName) + { + this.CacheInit(); + } + + public EntityXmlPersistentComplexRepositoryWithCache(string path, string elementName) : base(path, elementName) + { + this.CacheInit(); + } + + private Dictionary GetCacheDictionary() + { + return null; + //HttpContext httpContext = HttpContext.Current; + //if (httpContext == null) return null; + + //var cacheDictionary = (Dictionary)httpContext.Items[_cacheKey]; + //if (cacheDictionary == null) + //{ + // cacheDictionary = new Dictionary(); + // httpContext.Items[_cacheKey] = cacheDictionary; + //} + //return cacheDictionary; + } + + private TEntity GetFromCache(string key, Func getAction) + { + TEntity entity = null; + if (_cacheOn) + { + Dictionary cacheDictionary = this.GetCacheDictionary(); + if (cacheDictionary != null) + { + entity = cacheDictionary.GetByKey(key); + if (entity == null) + { + entity = getAction(); + cacheDictionary.SetByKey(key, entity); + } + } + } + return entity ?? getAction(); + } + + public override TEntity Get(XmlDocument db, TKey key) + { + string projectGuid = db.DocumentElement.GetValueAttribute("guid", string.Empty); + string cacheKey = "{0}_{1}".ApplyFormat(projectGuid, key); + + return this.GetFromCache(cacheKey, () => base.Get(db, key)); + } + + public TEntity Get1(XmlNode xmlNode, TKey key) + { + TEntity entity = this.ConvertToEntity(xmlNode); + return entity; + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Repository/Xml/EntityXmlPersistentSimpleRepository.cs b/LibCommon/Kit.Core.Helpers/Repository/Xml/EntityXmlPersistentSimpleRepository.cs new file mode 100644 index 0000000..7f5f3ee --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/Xml/EntityXmlPersistentSimpleRepository.cs @@ -0,0 +1,193 @@ +using Kit.Helpers; +using Kit.Helpers.Repository.Converter; +using Kit.Helpers.Repository.Xml.Exceptions; +using System.Xml; + +namespace Kit.Helpers.Repository.Xml +{ + public abstract class XmlSimpleClass : IPersistentKey, IContentXmlKey + { + public TKey Id { get; set; } + public TContentKey ContentId { get; set; } + public TKey PersistentKey + { + get { return this.Id; } + } + + public TContentKey GetContentKey + { + get { return this.ContentId; } + } + + public int RowVersion { get; set; } + } + + public abstract class XmlSimpleClassDb : XmlSimpleClass { } + + public abstract class EntityXmlPersistentSimpleRepository : + EntityXmlSelectSimpleRepository, + IPersistentXml2Repository + where TEntity : XmlSimpleClass + where TFilter : IXmlSimpleFilter + { + protected string _elementsAfterPostion; + + public EntityXmlPersistentSimpleRepository(string elementName) : base(elementName) + { + } + + public EntityXmlPersistentSimpleRepository(string elementName, IConverter converter) : base(elementName, converter) + { + } + + public EntityXmlPersistentSimpleRepository(string rootElementName, string path, string elementName) : base(rootElementName, path, elementName) + { + } + + public EntityXmlPersistentSimpleRepository(string path, string elementName) : base(path, elementName) + { + } + + public EntityXmlPersistentSimpleRepository(string path, string elementName, IConverter converter) : base(path, elementName, converter) + { + } + + public EntityXmlPersistentSimpleRepository(string rootElementName, string path, string elementName, IConverter converter) : base(rootElementName, path, elementName, converter) + { + } + + public EntityXmlPersistentSimpleRepository(string elementsAfterPostion, string rootElementName, string path, string elementName, IConverter converter) : base(rootElementName, path, elementName, converter) + { + _elementsAfterPostion = elementsAfterPostion; + } + + protected virtual XmlNode GetElementAfterInsert(XmlNode root, XmlNode persistent = null) + { + if (!string.IsNullOrEmpty(_elementsAfterPostion)) + { + var elements = root.SelectNodes(_elementsAfterPostion); + return elements.Cast().LastOrDefault(); + } + return null; + } + + + protected abstract IEnumerable SetPersistentFromEntity(XmlDocument db, XmlNode persistent, TEntity entity); + + protected virtual bool OnInserting(XmlDocument db, XmlNode persistent, TEntity entity) { return true; } + protected virtual bool OnInserted(XmlDocument db, XmlNode persistent, TEntity entity) { return true; } + + public TKey Insert(XmlDocument db, TEntity entity) + { + XmlNode parent = GetParentNode(db); + XmlNode persistent = parent.OwnerDocument.CreateNode(XmlNodeType.Element, _elementName, ""); + this.SetPersistentFromEntity(parent.OwnerDocument, persistent, entity); + if (this.OnInserting(db, persistent, entity)) + { + var after = GetElementAfterInsert(parent, persistent); + if (after == null) + { + if (parent.FirstChild == null) + { + parent.AppendChild(persistent); + } + else + { + parent.InsertBefore(persistent, parent.FirstChild); + } + } + else + { + parent.InsertAfter(persistent, after); + } + this.OnInserted(db, persistent, entity); + } + + return entity.PersistentKey; + } + + + public virtual TKey InsertToNode(XmlNode db, TEntity entity) + { + XmlNode parent = GetParentNode(db); + XmlNode persistent = parent.OwnerDocument.CreateNode(XmlNodeType.Element, _elementName, ""); + this.SetPersistentFromEntity(parent.OwnerDocument, persistent, entity); + + var after = GetElementAfterInsert(parent, persistent); + if (after == null) parent.InsertBefore(persistent, parent.FirstChild); + else + { + parent.InsertAfter(persistent, after); + } + + return entity.PersistentKey; + } + + protected virtual bool OnUpdating(XmlDocument db, XmlNode persistent, TEntity entity) { return true; } + protected virtual bool OnUpdated(XmlDocument db, XmlNode persistent, TEntity entity) { return true; } + + private XmlNodeList SelectNodesByKey(XmlDocument db, TKey key) + { + string xPath = $"{_path}/{_elementName}"; + + if (xPath.StartsWith("/") == false) + { + xPath = "//" + xPath; + } + + XmlNodeList list = db.SelectNodes($"{xPath}[@guid=\"{key}\"]"); + if (list.Count == 0) + { + list = db.SelectNodes($"{xPath}[@guid=\"{key.ToString().ToUpper()}\"]"); + } + + return list; + } + + public virtual void Delete(XmlDocument db, TKey key) + { + XmlNode persistent = SelectNodesByKey(db, key).Cast().AsQueryable().FirstOrDefault(); + persistent.ParentNode.RemoveChild(persistent); + } + + public virtual TEntity Get(XmlDocument db, TKey key) + { + return Get(db, key, default); + } + + public virtual TEntity Get(XmlDocument db, TKey key, TFilter filter) + { + XmlNodeList list = SelectNodesByKey(db, key); + return this.FillResult(db, list.Cast().AsQueryable().ToList().Select(x => this.FillXmlClass(this.ConvertToEntity(x, filter), x, filter)), filter).SingleOrDefault(); + } + + public virtual bool Exists(XmlDocument db, TKey key) + { + XmlNodeList list = SelectNodesByKey(db, key); + return list.Count > 0; + } + + protected virtual System.Linq.Expressions.Expression> GetByKeyExpression(TKey key) + { + return x => x.Attributes["guid"].InnerText == key.ToString(); + } + + public IEnumerable Update(XmlDocument db, TEntity entity) + { + XmlNode persistent = SelectNodesByKey(db, entity.PersistentKey).Cast().AsQueryable().FirstOrDefault(); + IEnumerable? itemLogs = null; + + if (this.OnUpdating(db, persistent, entity)) + { + //int row = persistent.GetValueAttributeInt32("rowVersion"); + int row = entity.RowVersion; + if (row != entity.RowVersion) throw new RowVersionException("Данные были изменены версии записи не совпадают"); + + itemLogs = this.SetPersistentFromEntity(db, persistent, entity); + persistent.SetAttributeValue("rowVersion", (++row).ToString()); + this.OnUpdated(db, persistent, entity); + } + return itemLogs ?? Array.Empty(); + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Repository/Xml/EntityXmlPersistentSimpleRepositoryWithCache.cs b/LibCommon/Kit.Core.Helpers/Repository/Xml/EntityXmlPersistentSimpleRepositoryWithCache.cs new file mode 100644 index 0000000..6e4d3b7 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/Xml/EntityXmlPersistentSimpleRepositoryWithCache.cs @@ -0,0 +1,101 @@ +using Microsoft.AspNetCore.Http; +using System.Xml; + +namespace Kit.Helpers.Repository.Xml +{ + public abstract class EntityXmlPersistentSimpleRepositoryWithCache : EntityXmlPersistentSimpleRepository + where TEntity : XmlSimpleClass + where TFilter : IXmlSimpleFilter + { + private string _cacheKey; + protected bool _cacheOn; + + private void CacheInit() + { + _cacheKey = this.GetType().FullName; + _cacheOn = true; + } + + public EntityXmlPersistentSimpleRepositoryWithCache(string elementName) + : base(elementName) + { + this.CacheInit(); + } + + public EntityXmlPersistentSimpleRepositoryWithCache(string path, string elementName) : base(path, elementName) + { + this.CacheInit(); + } + //public EntityXmlPersistentSimpleRepository2(string elementName, TContext data, IConverter converter) + // : base(elementName, data, converter) + //{ + //} + //public EntityXmlPersistentSimpleRepository2(string rootElementName, string path, string elementName, TContext data); + //public EntityXmlPersistentSimpleRepository2(string path, string elementName, TContext data, IConverter converter); + //public EntityXmlPersistentSimpleRepository2(string rootElementName, string path, string elementName, TContext data, IConverter converter); + //public EntityXmlPersistentSimpleRepository2(string elementsAfterPostion, string rootElementName, string path, string elementName, TContext data, IConverter converter); + + private Dictionary? GetCacheDictionary(ISelectContext selectContext) + { + HttpContext? httpContext = HttpContextCore.Current; + if (httpContext == null && selectContext == null) + { + return null; + } + + IDictionary? items = selectContext != null ? selectContext.Items : httpContext!.Items; + + lock (items) + { + Dictionary cacheDictionary = (Dictionary)items[_cacheKey]; + + if (cacheDictionary == null) + { + cacheDictionary = new Dictionary(); + items[_cacheKey] = cacheDictionary; + } + + return cacheDictionary; + } + } + + private TEntity GetFromCache(string key, Func getAction, ISelectContext selectContext = null) + { + TEntity entity = null; + if (_cacheOn) + { + Dictionary? cacheDictionary = this.GetCacheDictionary(selectContext); + if (cacheDictionary != null) + { + lock (cacheDictionary) + { + entity = cacheDictionary.GetByKey(key); + if (entity == null) + { + entity = getAction(); + cacheDictionary.SetByKey(key, entity); + } + } + } + } + + return entity ?? getAction(); + } + + public override TEntity Get(XmlDocument db, TKey key) + { + string projectGuid = db.DocumentElement.GetValueAttribute("guid", string.Empty); + string cacheKey = "{0}_{1}".ApplyFormat(projectGuid, key); + + return this.GetFromCache(cacheKey, () => base.Get(db, key)); + } + + public override TEntity Get(XmlDocument db, TKey key, TFilter filter) + { + string projectGuid = db.DocumentElement.GetValueAttribute("guid", string.Empty); + string cacheKey = "{0}_{1}".ApplyFormat(projectGuid, key); + + return null;// this.GetFromCache(cacheKey, () => base.Get(db, key, filter), (ISelectContext)filter.GetPropertyValue("SelectContext")); + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Repository/Xml/EntityXmlRepository.cs b/LibCommon/Kit.Core.Helpers/Repository/Xml/EntityXmlRepository.cs new file mode 100644 index 0000000..f008bb4 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/Xml/EntityXmlRepository.cs @@ -0,0 +1,141 @@ +namespace Kit.Helpers.Repository.Xml +{ + public enum UpdateStatuses { Ok = 1, DocumentRowVersionNotChecked = 2, RowVersionNotChecked = 3, Cancel = 4 } + + public abstract class EntityXmlRepository + { + protected string _elementName; + protected string _path = "{0}"; + protected string _rootElementName; + + public EntityXmlRepository(string elementName) + { + _elementName = elementName; + } + + public EntityXmlRepository(string path, string elementName) + { + _elementName = elementName; + _path = path; + } + + public EntityXmlRepository(string rootElementName, string path, string elementName) + { + _elementName = elementName; + _rootElementName = rootElementName; + _path = path; + } + } + + public interface IXmlFilter + { + TKey GetRootKey(); + TContentKey GetXmlContentKey(); + } + + public class XmlFilter : IXmlFilter + { + public int RootXml { get; set; } + public Guid RootKey { get; set; } + public Guid? Id { get; set; } + public int GetXmlContentKey() + { + return this.RootXml; + } + public Guid GetRootKey() + { + return RootKey; + } + } + + public class XmlFilterInt : IXmlFilter + { + public int RootXml { get; set; } + public int RootKey { get; set; } + public int? Id { get; set; } + public int GetXmlContentKey() + { + return this.RootXml; + } + public int GetRootKey() + { + return RootKey; + } + } + + public interface IRootXmlKey + { + TKey RootId { get; } + } + + + + public interface IContentXmlKey + { + TKey GetContentKey { get; } + } + + public abstract class XmlSimpleClass : IPersistentKey, IPersistentXmlKey + { + public TKey Id { get; set; } + public TKey ParentId { get; set; } + public string ElementNameParent { get; set; } + public string ElementName { get; set; } + public int RowVersion { get; set; } + public TKey PersistentKey + { + get { return this.Id; } + } + public TKey PersistentXmlRootKey + { + get { return this.ParentId; } + } + } + + public abstract class XmlSimpleClass : XmlSimpleClass { } + public abstract class XmlFilterSimpleDb : IXmlSimpleFilter + { + public int RootXml { get; set; } + public Guid? Id { get; set; } + public ISelectContext SelectContext { get; set; } + + public virtual int GetXmlContentKey() + { + return this.RootXml; + } + + public Guid GetKey() + { + return Id ?? Guid.Empty; + } + } + public abstract class XmlFilterSimple : IXmlSimpleFilter + where TKey: struct + { + public TContentKey RootXml { get; set; } + public TKey? Id { get; set; } + public ISelectContext SelectContext { get; set; } + + public virtual TContentKey GetXmlContentKey() + { + return this.RootXml; + } + + public TKey GetKey() + { + return Id ?? default; + } + } + + public abstract class XmlFilterComplex : XmlFilterSimple, IXmlComplexFilter + where TKey: struct + where TRootKey : struct + { + public TRootKey RootKey { get; set; } + + public virtual TRootKey RootKeyId() + { + return RootKey; + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Repository/Xml/EntityXmlSelectComplexRepository.cs b/LibCommon/Kit.Core.Helpers/Repository/Xml/EntityXmlSelectComplexRepository.cs new file mode 100644 index 0000000..c6bc71e --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/Xml/EntityXmlSelectComplexRepository.cs @@ -0,0 +1,103 @@ +using Kit.Helpers.Repository.Converter; +using System.Xml; + +namespace Kit.Helpers.Repository.Xml +{ + public interface IXmlComplexFilter : IXmlSimpleFilter + { + TRootKey RootKeyId(); + } + public abstract class XmlComplexClass : XmlSimpleClass, IRootXmlKey + { + public TRootKey RootId { get; set; } + } + + public abstract class EntityXmlSelectComplexRepository : + EntityXmlSelectSimpleRepository + where TFilter : IXmlComplexFilter + where TEntity : XmlComplexClass + { + + public EntityXmlSelectComplexRepository(string parentElementName, string path, string element, IConverter converter) : base(parentElementName, path, element) + { + _converter = converter; + } + + public EntityXmlSelectComplexRepository(string path, string element, IConverter converter) : base(path, element) + { + _converter = converter; + } + + + public EntityXmlSelectComplexRepository(string element, IConverter converter) : base(element) + { + _converter = converter; + } + + + public EntityXmlSelectComplexRepository(string element) : base(element) + { + } + + public EntityXmlSelectComplexRepository(string path, string element) : base(path, element) + { + } + + public EntityXmlSelectComplexRepository(string rootElementName, string path, string element) : base(rootElementName, path, element) + { + } + + protected virtual XmlNodeList SelectNodesByKey(XmlDocument db, TKey key) + { + string xPath = $"{_elementName}"; + + if (string.IsNullOrWhiteSpace(_path) == false) + { + xPath = $"{_path}/{xPath}"; + } + + if (xPath.StartsWith("/") == false) + { + xPath = "//" + xPath; + } + + XmlNodeList list = db.SelectNodes($"{xPath}[@guid=\"{key}\"]"); + if (list.Count == 0) + { + list = db.SelectNodes($"{xPath}[@guid=\"{key.ToString().ToUpper()}\"]"); + } + + return list; + } + + public virtual XmlNode GetRootNode(XmlDocument document, TRootKey key) + { + XmlNode result = document.SelectSingleNode("//*[@guid=\"{0}\"]".ApplyFormat(key.ToString())); + if (result == null) + result = document.SelectSingleNode("//*[@guid=\"{0}\"]".ApplyFormat(key.ToString().ToUpper())); + if (!string.IsNullOrEmpty(_rootElementName)) + { + var node = result.SelectSingleNode(_rootElementName); + if (node == null) + { + node = result.OwnerDocument.CreateElement(_rootElementName); + result.AppendChild(node); + } + result = node; + } + + return result; + } + + public override IQueryable CreateQuery(XmlDocument db, TFilter filter) + { + XmlNode root = GetRootNode(db, filter.RootKeyId()); + if (root == null) + root = db.CreateElement("Empty"); + XmlNode parent = GetParentNode(root); + if (parent == null) + parent = db.CreateElement("Empty"); + return parent.SelectNodes(this._elementName).Cast().AsQueryable(); + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Repository/Xml/EntityXmlSelectSimpleRepository.cs b/LibCommon/Kit.Core.Helpers/Repository/Xml/EntityXmlSelectSimpleRepository.cs new file mode 100644 index 0000000..ef2e792 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/Xml/EntityXmlSelectSimpleRepository.cs @@ -0,0 +1,160 @@ +using System.Xml; +using Kit.Helpers; +using Kit.Helpers.Repository.Converter; + +namespace Kit.Helpers.Repository.Xml +{ + public interface IXmlSimpleFilter + { + TKey GetKey(); + TContentKey GetXmlContentKey(); + } + + public abstract class EntityXmlSelectSimpleRepository : + EntityXmlRepository, + ISelectPageXmlRepository + where TFilter : IXmlSimpleFilter + where TEntity : IPersistentKey + { + public EntityXmlSelectSimpleRepository(string parentElementName, string path, string element, IConverter converter) + : base(parentElementName, path, element) + { + _converter = converter; + } + + public EntityXmlSelectSimpleRepository(string path, string element, IConverter converter) : base(path, element) + { + _converter = converter; + } + + + public EntityXmlSelectSimpleRepository(string element, IConverter converter) : base(element) + { + _converter = converter; + } + + + public EntityXmlSelectSimpleRepository(string element) : base(element) + { + } + + public EntityXmlSelectSimpleRepository(string path, string element) : base(path, element) + { + } + + public EntityXmlSelectSimpleRepository(string rootElementName, string path, string element) : base(rootElementName, path, element) + { + } + + protected IConverter _converter = null; + + protected virtual TEntity ConvertToEntity(XmlNode persistent) + { + return _converter.Convert(persistent); + } + + protected virtual TEntity ConvertToEntity(XmlNode persistent, TFilter filter) + { + return this.ConvertToEntity(persistent); + } + + public virtual XmlNode GetParentNode(XmlNode root) + { + XmlNode result = root.SelectSingleNode(_path); + if (!string.IsNullOrEmpty(_rootElementName)) + { + var node = result.SelectSingleNode(_rootElementName); + if (node == null) + { + node = result.OwnerDocument.CreateElement(_rootElementName); + result.AppendChild(node); + } + result = node; + } + + return result; + } + + public virtual IQueryable CreateQuery(XmlDocument db, TFilter filter) + { + XmlNode parent = GetParentNode(db); + + if (parent == null) parent = db.CreateElement("Empty"); + return parent.SelectNodes(this._elementName).Cast().AsQueryable(); + } + + public virtual IQueryable CreateQuery(XmlNode node, TFilter filter) + { + XmlNode parent = GetParentNode(node); + + if (parent == null) parent = node.OwnerDocument.CreateElement("Empty"); + return parent.SelectNodes(this._elementName).Cast().AsQueryable(); + } + + protected virtual IQueryable ApplyFilter(IQueryable query, TFilter filter) + { + return query; + } + + protected virtual TEntity FillXmlClass(TEntity entity, XmlNode persistent, TFilter filter) + { + return entity; + } + + protected IQueryable SelectNodesByFilter(XmlDocument db, TFilter filter) + { + IQueryable query = this.CreateQuery(db, filter); + query = this.ApplyFilter(query, filter); + + return query; + } + + public virtual IEnumerable Select(XmlDocument db, TFilter filter) + { + IQueryable query = this.SelectNodesByFilter(db, filter); + query = this.ApplyFilter(query, filter); + var tt = query.ToList() + .Select(x => this.FillXmlClass(this.ConvertToEntity(x, filter), x, filter)) + .ToList(); + + return this.FillResult(db, tt, filter); + } + + public virtual IEnumerable Select(XmlNode node, TFilter filter) + { + IQueryable query = this.CreateQuery(node, filter); + query = this.ApplyFilter(query, filter); + var tt = query.ToList() + .Select(x => this.FillXmlClass(this.ConvertToEntity(x, filter), x, filter)) + .ToList(); + + return this.FillResult(node.OwnerDocument ?? (XmlDocument)node, tt, filter); + } + + public virtual IEnumerableWithPage SelectPage(XmlDocument db, TFilter filter, int pageNo, int pageSize) + { + IQueryable query = this.CreateQuery(db, filter); + query = this.ApplyFilter(query, filter); + var result = new ListWithPage(); + result.TotalRows = query.Count(); + result.PageNo = pageNo; + result.PageSize = pageSize; + var tt = query.Skip((pageNo - 1) * pageSize).Take(pageSize) + .Select(x => this.FillXmlClass(this.ConvertToEntity(x, filter), x, filter)) + .ToList(); + result.AddRange(FillResult(db, tt, filter)); + + return result; + } + + public virtual IEnumerable FillResult(XmlDocument db, IEnumerable entities, TFilter filter) { return entities; } + + public int Count(XmlDocument db, TFilter filter) + { + IQueryable query = this.SelectNodesByFilter(db, filter); + { + return query.Count(); + } + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Repository/Xml/Exceptions/RowVersionException.cs b/LibCommon/Kit.Core.Helpers/Repository/Xml/Exceptions/RowVersionException.cs new file mode 100644 index 0000000..f966d67 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/Xml/Exceptions/RowVersionException.cs @@ -0,0 +1,28 @@ +using Kit.Helpers.Log; +using System; + +namespace Kit.Helpers.Repository.Xml.Exceptions +{ + public class RowVersionException : Exception + { + public IOperationLogger OperationLogger = Log.OperationLogger.Empty; + public RowVersionException() + { + } + + public RowVersionException(string message) + : base(message) + { + } + + public RowVersionException(string message, Exception inner) + : base(message, inner) + { + } + public RowVersionException(string message, IOperationLogger operationLogger) + : base(message) + { + OperationLogger = operationLogger; + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Repository/Xml/IContentXml.cs b/LibCommon/Kit.Core.Helpers/Repository/Xml/IContentXml.cs new file mode 100644 index 0000000..1a2b40a --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/Xml/IContentXml.cs @@ -0,0 +1,13 @@ +using System.Xml; + +namespace Kit.Helpers.Repository.Xml +{ + public interface IContentXml + { + XmlDocument CreateDefault(); + XmlDocument? GetContentData(TContentKey key); + bool CreateContentData(TContentKey key, XmlDocument document, bool skipIfExists = true); + void UpdateContentData(TContentKey key, XmlDocument document); + int GetRowVersion(XmlDocument document); + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Repository/Xml/IPersistentRepository.cs b/LibCommon/Kit.Core.Helpers/Repository/Xml/IPersistentRepository.cs new file mode 100644 index 0000000..3640d83 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/Xml/IPersistentRepository.cs @@ -0,0 +1,32 @@ +namespace Kit.Helpers.Repository.Xml +{ + public interface IPersistentKey + { + TKey PersistentKey { get; } + } + + public interface IInt32PersistentKey : IPersistentKey { } + + public interface ISelectRepository + { + IEnumerable Select(TFilter filter); + + int Count(TFilter filter); + IEnumerable Select(TFilter filter, int page, int sizepage); + } + + public class NothingFilter { } + + public interface IPersistentRepository : ISelectRepository + { + TKey Insert(TEntity entity); + void Update(TKey key, TEntity entity); + void Delete(TKey key); + TEntity Get(TKey key); + // IEnumerable Get(IEnumerable keys); + } + + public interface IPersistentRepository : IPersistentRepository + { + } +} diff --git a/LibCommon/Kit.Core.Helpers/Repository/Xml/IPersistentXmlRepository.cs b/LibCommon/Kit.Core.Helpers/Repository/Xml/IPersistentXmlRepository.cs new file mode 100644 index 0000000..d5f8a10 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/Xml/IPersistentXmlRepository.cs @@ -0,0 +1,55 @@ +using System.Xml; + +namespace Kit.Helpers.Repository.Xml +{ + public interface IPersistentXmlKey + { + string ElementName { get; set; } + TKey PersistentXmlRootKey { get; } + } + + public interface ISelectXmlRepository + { + IEnumerable Select(XmlDocument db, TFilter filter); + /// Получить количество xml узлов, подходящих под фильтр. Облегчённый аналог .Count() + int Count(XmlDocument db, TFilter filter); + IEnumerable Select(XmlDocument db, TFilter filter, int page, int sizepage); + } + + public interface ISelectPageXmlRepository + { + IEnumerable Select(XmlDocument db, TFilter filter); + IEnumerable Select(XmlNode node, TFilter filter); + /// Получить количество xml узлов, подходящих под фильтр. Облегчённый аналог .Count() + int Count(XmlDocument db, TFilter filter); + IEnumerableWithPage SelectPage(XmlDocument db, TFilter filter, int pageNo, int pageSize); + } + + public interface IPersistentXml2Repository : ISelectPageXmlRepository + { + TEntity Get(XmlDocument db, TKey key); + TEntity Get(XmlDocument db, TKey key, TFilter filter); + bool Exists(XmlDocument db, TKey key); + IEnumerable Update(XmlDocument db, TEntity entity); + TKey InsertToNode(XmlNode db, TEntity entity); + TKey Insert(XmlDocument db, TEntity entity); + void Delete(XmlDocument db, TKey key); + // IEnumerable Get(IEnumerable keys); + } + + + public interface IPersistentXmlRepository : ISelectXmlRepository + { + TEntity Get(XmlDocument db, TKey key); + TKey Insert(XmlNode db, TEntity entity); + // IEnumerable Get(IEnumerable keys); + } + + public interface IPersistentXmlRepository : IPersistentXmlRepository + { + } + + public interface IPersistentXmlRepository : IPersistentXmlRepository + { + } +} diff --git a/LibCommon/Kit.Core.Helpers/Repository/Xml/RowVersionRepository.cs b/LibCommon/Kit.Core.Helpers/Repository/Xml/RowVersionRepository.cs new file mode 100644 index 0000000..a5d6002 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/Xml/RowVersionRepository.cs @@ -0,0 +1,29 @@ +using System.Xml; + +namespace Kit.Helpers.Repository.Xml; +public interface IRowVersionRepository +{ + int GetRowVersion(XmlNode xmlNode); + void SetRowVersion(XmlNode xmlNode, int newVersionId); + int IncreaseRowVersion(XmlNode xmlNode); +} + +public class RowVersionRepository : IRowVersionRepository +{ + public RowVersionRepository() { } + + private readonly string _attrNameRowVersion = "RowVersion"; + + public int GetRowVersion(XmlNode xmlNode) => xmlNode.GetValueAttributeInt32(_attrNameRowVersion); + + public void SetRowVersion(XmlNode xmlNode, int newVersionId) => xmlNode.SetAttributeValue(_attrNameRowVersion, newVersionId.ToString()); + + public int IncreaseRowVersion(XmlNode xmlNode) + { + int newVersionId = GetRowVersion(xmlNode) + 1; + + SetRowVersion(xmlNode, newVersionId); + + return newVersionId; + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Repository/Xml/XmlFileVersionRepository.cs b/LibCommon/Kit.Core.Helpers/Repository/Xml/XmlFileVersionRepository.cs new file mode 100644 index 0000000..4324054 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Repository/Xml/XmlFileVersionRepository.cs @@ -0,0 +1,49 @@ +using System.Xml; + +namespace Kit.Helpers.Repository.Xml; +public class XmlFileVersionInfo +{ + public XmlFileVersionInfo() + { + Type = string.Empty; + Version = 0; + } + + public string Type { get; set; } + public long Version { get; set; } +} + + +public interface IXmlFileVersionRepository +{ + XmlFileVersionInfo? Get(XmlDocument document); + void Set(XmlDocument document, XmlFileVersionInfo info); +} + +public class XmlFileVersionRepository : IXmlFileVersionRepository +{ + public XmlFileVersionRepository() { } + + public XmlFileVersionInfo? Get(XmlDocument document) + { + XmlNode? fileVersionNode = document.DocumentElement?.SelectSingleNode("FileVersion"); + if (fileVersionNode == null) return null; + + return new XmlFileVersionInfo + { + Type = fileVersionNode.GetValueAttribute("type"), + Version = fileVersionNode.GetValueAttributeInt64("version"), + }; + } + + public void Set(XmlDocument document, XmlFileVersionInfo info) + { + Check.IsNotNull(info, "info is not set"); + Check.IsNotNull(document.DocumentElement, "DocumentElement is not set"); + + XmlNode fileVersionNode = document.DocumentElement.SetElementValue("FileVersion", string.Empty); + + fileVersionNode.SetAttributeValue("type", info.Type); + fileVersionNode.SetAttributeValue("version", info.Version.ToString()); + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Routes/ControllerBaseRouteFluent.cs b/LibCommon/Kit.Core.Helpers/Routes/ControllerBaseRouteFluent.cs new file mode 100644 index 0000000..10a91ac --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Routes/ControllerBaseRouteFluent.cs @@ -0,0 +1,392 @@ +namespace Kit.Helpers.Routes +{ + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Routing; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + + public class ControllerBaseRouteFluent : IEndPointFluent> + where TController : ControllerBase + { + private readonly IRouteBuilder _routeTable; + private readonly List _routes; + + public ControllerBaseRouteFluent(IRouteBuilder routeTable) + { + _routeTable = routeTable; + _routes = new List(); + } + + #region Generic Result Actions + + private void AddRouteLambda(string url, LambdaExpression actionSelector) + { + // получаем имя контроллера + string controllerName = typeof(TController).Name; + if (controllerName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) && controllerName.Length > "Controller".Length) + { + controllerName = controllerName.Remove(controllerName.Length - "Controller".Length, "Controller".Length); + } + + // получаем имя действия + var unaryExpression = (UnaryExpression)actionSelector.Body; + var methodCallExpression = (MethodCallExpression)unaryExpression.Operand; + var constantExpression = (ConstantExpression)methodCallExpression.Object; + var methodInfo = (MethodInfo)constantExpression.Value; + string actionName = methodInfo.Name; + + _routes.Add( _routeTable.AddRoute($"{controllerName}_{actionName}_{Guid.NewGuid()}",url, controllerName, actionName, methodInfo)); + } + + public delegate TResult GenericResult0Delegate(); + public delegate TResult GenericResult1Delegate(TInput1 input1); + public delegate TResult GenericResult2Delegate(TInput1 input1, TInput2 input2); + public delegate TResult GenericResult3Delegate(TInput1 input1, TInput2 input2, TInput3 input3); + public delegate TResult GenericResult4Delegate(TInput1 input1, TInput2 input2, TInput3 input3, TInput4 input4); + public delegate TResult GenericResult5Delegate(TInput1 input1, TInput2 input2, TInput3 input3, TInput4 input4, TInput5 input5); + + public ControllerBaseRouteFluent GenericAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent GenericAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent GenericAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent GenericAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent GenericAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + public ControllerBaseRouteFluent GenericAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + #endregion + + #region JsonResult Actions + + public ControllerBaseRouteFluent JsonAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent JsonAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent JsonAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent JsonAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent JsonAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent JsonAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + #endregion + + #region EmptyResult Actions + + public ControllerBaseRouteFluent EmptyAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent EmptyAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent EmptyAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent EmptyAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent EmptyAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent EmptyAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + #endregion + + #region ContentResult Actions + + public ControllerBaseRouteFluent ContentAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent ContentAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent ContentAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent ContentAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent ContentAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent ContentAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + #endregion + + #region PartialViewResult Actions + + public ControllerBaseRouteFluent PartialViewAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent PartialViewAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent PartialViewAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent PartialViewAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent PartialViewAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent PartialViewAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + #endregion + + #region ActionResult Actions + + public ControllerBaseRouteFluent ActionAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent ActionAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent ActionAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent ActionAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent ActionAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + public ControllerBaseRouteFluent ActionAction(string url, Expression>> actionSelector) + { + this.AddRouteLambda(url, actionSelector); + return this; + } + + #endregion + + #region IRouteFluent> + + public ControllerBaseRouteFluent NeedHttpGet(bool required = true) + { + if (_routes.Any()) _routes.Last().NeedHttpGet(required); + return this; + } + + public ControllerBaseRouteFluent NeedHttpPost(bool required = true) + { + if (_routes.Any()) _routes.Last().NeedHttpPost(required); + return this; + } + + public ControllerBaseRouteFluent WithDefaults(object defaults) + { + if (_routes.Any()) _routes.Last().WithDefaults(defaults); + return this; + } + + public ControllerBaseRouteFluent WithConstraints(object constraints) + { + if (_routes.Any()) _routes.Last().WithConstraints(constraints); + return this; + } + + public ControllerBaseRouteFluent WithNamespaces(string[] namespaces) + { + if (_routes.Any()) _routes.Last().WithNamespaces(namespaces); + return this; + } + + public ControllerBaseRouteFluent NeedSecurityKey(string securityKeyName) + { + if (_routes.Any()) _routes.Last().NeedSecurityKey(securityKeyName); + return this; + } + + public ControllerBaseRouteFluent WithDataTokens(object dataTokens) + { + if (_routes.Any()) _routes.Last().WithDataTokens(dataTokens); + return this; + } + + public ControllerBaseRouteFluent WithDelegate(Action routeFluentAction) + { + if (_routes.Any()) routeFluentAction(_routes.Last()); + return this; + } + + #endregion + + #region IRouteFluent> for ALL + + public ControllerBaseRouteFluent NeedHttpGet_All(bool required = true) + { + _routes.ForEach(x => x.NeedHttpGet(required)); + return this; + } + + public ControllerBaseRouteFluent NeedHttpPost_All(bool required = true) + { + _routes.ForEach(x => x.NeedHttpPost(required)); + return this; + } + + public ControllerBaseRouteFluent WithDefaults_All(object defaults) + { + _routes.ForEach(x => x.WithDefaults(defaults)); + return this; + } + + public ControllerBaseRouteFluent WithConstraints_All(object constraints) + { + _routes.ForEach(x => x.WithConstraints(constraints)); + return this; + } + + public ControllerBaseRouteFluent WithNamespaces_All(string[] namespaces) + { + _routes.ForEach(x => x.WithNamespaces(namespaces)); + return this; + } + + public ControllerBaseRouteFluent NeedSecurityKey_All(string securityKeyName) + { + _routes.ForEach(x => x.NeedSecurityKey(securityKeyName)); + return this; + } + + public ControllerBaseRouteFluent WithDataTokens_All(object dataTokens) + { + _routes.ForEach(x => x.WithDataTokens(dataTokens)); + return this; + } + + public ControllerBaseRouteFluent WithDelegate_All(Action routeFluentAction) + { + _routes.ForEach(x => routeFluentAction(x)); + return this; + } + + #endregion + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Routes/ControllerRouteFluent.cs b/LibCommon/Kit.Core.Helpers/Routes/ControllerRouteFluent.cs new file mode 100644 index 0000000..9308e2a --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Routes/ControllerRouteFluent.cs @@ -0,0 +1,174 @@ +namespace Kit.Helpers.Routes +{ + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Routing; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + + public partial class ControllerRouteFluent : IEndPointFluent> + where TController : Controller + { + private readonly IRouteBuilder _routeTable; + private readonly List _routes; + + public ControllerRouteFluent(IRouteBuilder routeTable) + { + _routeTable = routeTable; + _routes = new List(); + } + + #region Generic Result Actions + + private ControllerRouteFluent AddRouteLambda(string url, LambdaExpression actionSelector) + { + // получаем имя контроллера + string controllerName = typeof(TController).Name; + if (controllerName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) && controllerName.Length > "Controller".Length) + { + controllerName = controllerName.Remove(controllerName.Length - "Controller".Length, "Controller".Length); + } + + // получаем имя действия + var unaryExpression = (UnaryExpression)actionSelector.Body; + var methodCallExpression = (MethodCallExpression)unaryExpression.Operand; + var constantExpression = (ConstantExpression)methodCallExpression.Object; + var methodInfo = (MethodInfo)constantExpression.Value; + string actionName = methodInfo.Name; + + _routes.Add(_routeTable.AddRoute($"{controllerName}_{actionName}_{Guid.NewGuid()}", url, controllerName, actionName, methodInfo)); + return this; + } + + #endregion + + #region IRouteFluent> + + public ControllerRouteFluent NeedHttpGet(bool required = true) + { + if (_routes.Any()) _routes.Last().NeedHttpGet(required); + return this; + } + + public ControllerRouteFluent NeedHttpPost(bool required = true) + { + if (_routes.Any()) _routes.Last().NeedHttpPost(required); + return this; + } + + public ControllerRouteFluent NeedHttpPut(bool required = true) + { + if (_routes.Any()) _routes.Last().NeedHttpPut(required); + return this; + } + + public ControllerRouteFluent NeedHttpDelete(bool required = true) + { + if (_routes.Any()) _routes.Last().NeedHttpDelete(required); + return this; + } + + public ControllerRouteFluent WithBody(bool required = true) + { + if (_routes.Any()) _routes.Last().WithBody(required); + return this; + } + + public ControllerRouteFluent WithDefaults(object defaults) + { + if (_routes.Any()) _routes.Last().WithDefaults(defaults); + return this; + } + + public ControllerRouteFluent WithConstraints(object constraints) + { + if (_routes.Any()) _routes.Last().WithConstraints(constraints); + return this; + } + + public ControllerRouteFluent WithNamespaces(string[] namespaces) + { + if (_routes.Any()) _routes.Last().WithNamespaces(namespaces); + return this; + } + + public ControllerRouteFluent NeedSecurityKey(string securityKeyName) + { + if (_routes.Any()) _routes.Last().NeedSecurityKey(securityKeyName); + return this; + } + + public ControllerRouteFluent WithDataTokens(object dataTokens) + { + if (_routes.Any()) _routes.Last().WithDataTokens(dataTokens); + return this; + } + + public ControllerRouteFluent WithDelegate(Action routeFluentAction) + { + if (_routes.Any()) routeFluentAction(_routes.Last()); + return this; + } + + public ControllerRouteFluent ForbidForReadonly(bool value = true) + { + if (_routes.Any()) _routes.Last().ForbidForReadonly(value); + return this; + } + #endregion + + #region IRouteFluent> for ALL + + public ControllerRouteFluent NeedHttpGet_All(bool required = true) + { + _routes.ForEach(x => x.NeedHttpGet(required)); + return this; + } + + public ControllerRouteFluent NeedHttpPost_All(bool required = true) + { + _routes.ForEach(x => x.NeedHttpPost(required)); + return this; + } + + public ControllerRouteFluent WithDefaults_All(object defaults) + { + _routes.ForEach(x => x.WithDefaults(defaults)); + return this; + } + + public ControllerRouteFluent WithConstraints_All(object constraints) + { + _routes.ForEach(x => x.WithConstraints(constraints)); + return this; + } + + public ControllerRouteFluent WithNamespaces_All(string[] namespaces) + { + _routes.ForEach(x => x.WithNamespaces(namespaces)); + return this; + } + + public ControllerRouteFluent NeedSecurityKey_All(string securityKeyName) + { + _routes.ForEach(x => x.NeedSecurityKey(securityKeyName)); + return this; + } + + public ControllerRouteFluent WithDataTokens_All(object dataTokens) + { + _routes.ForEach(x => x.WithDataTokens(dataTokens)); + return this; + } + + public ControllerRouteFluent WithDelegate_All(Action routeFluentAction) + { + _routes.ForEach(x => routeFluentAction(x)); + return this; + } + + #endregion + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Routes/ControllerRouteFluentAutogen.cs b/LibCommon/Kit.Core.Helpers/Routes/ControllerRouteFluentAutogen.cs new file mode 100644 index 0000000..0d3cc2c --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Routes/ControllerRouteFluentAutogen.cs @@ -0,0 +1,402 @@ +namespace Kit.Helpers.Routes +{ + using System; + using System.Linq.Expressions; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Mvc; + + public partial class ControllerRouteFluent : IEndPointFluent> + where TController : Controller + { + public delegate TResult GenericResult0Delegate(); + public delegate Task GenericResult0DelegateAsync(); + + public delegate TResult GenericResult1Delegate(TInput1 input1); + public delegate Task GenericResult1DelegateAsync(TInput1 input1); + + public delegate TResult GenericResult2Delegate(TInput1 input1, TInput2 input2); + public delegate Task GenericResult2DelegateAsync(TInput1 input1, TInput2 input2); + + public delegate TResult GenericResult3Delegate(TInput1 input1, TInput2 input2, TInput3 input3); + public delegate Task GenericResult3DelegateAsync(TInput1 input1, TInput2 input2, TInput3 input3); + + public delegate TResult GenericResult4Delegate(TInput1 input1, TInput2 input2, TInput3 input3, TInput4 input4); + public delegate Task GenericResult4DelegateAsync(TInput1 input1, TInput2 input2, TInput3 input3, TInput4 input4); + + public delegate TResult GenericResult5Delegate(TInput1 input1, TInput2 input2, TInput3 input3, TInput4 input4, TInput5 input5); + public delegate Task GenericResult5DelegateAsync(TInput1 input1, TInput2 input2, TInput3 input3, TInput4 input4, TInput5 input5); + + + #region TResult Actions + + public ControllerRouteFluent GenericAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent GenericActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent GenericAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent GenericActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent GenericAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent GenericActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent GenericAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent GenericActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent GenericAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent GenericActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent GenericAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent GenericActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + #endregion + + + #region JsonResult Actions + + public ControllerRouteFluent JsonAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent JsonActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent JsonAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent JsonActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent JsonAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent JsonActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent JsonAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent JsonActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent JsonAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent JsonActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent JsonAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent JsonActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + #endregion + + + #region EmptyResult Actions + + public ControllerRouteFluent EmptyAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent EmptyActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent EmptyAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent EmptyActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent EmptyAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent EmptyActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent EmptyAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent EmptyActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent EmptyAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent EmptyActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent EmptyAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent EmptyActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + #endregion + + + #region ContentResult Actions + + public ControllerRouteFluent ContentAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent ContentActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent ContentAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent ContentActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent ContentAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent ContentActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent ContentAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent ContentActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent ContentAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent ContentActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent ContentAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent ContentActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + #endregion + + + #region ViewResult Actions + + public ControllerRouteFluent ViewAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent ViewActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent ViewAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent ViewActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent ViewAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent ViewActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent ViewAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent ViewActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent ViewAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent ViewActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent ViewAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent ViewActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + #endregion + + + #region PartialViewResult Actions + + public ControllerRouteFluent PartialViewAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent PartialViewActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent PartialViewAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent PartialViewActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent PartialViewAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent PartialViewActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent PartialViewAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent PartialViewActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent PartialViewAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent PartialViewActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent PartialViewAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent PartialViewActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + #endregion + + + #region IActionResult Actions + + public ControllerRouteFluent ActionAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent ActionActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent ActionAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent ActionActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent ActionAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent ActionActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent ActionAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent ActionActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent ActionAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent ActionActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + public ControllerRouteFluent ActionAction(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent ActionActionAsync(string url, Expression>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + + #endregion + + + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Routes/ControllerRouteFluentAutogen.tt b/LibCommon/Kit.Core.Helpers/Routes/ControllerRouteFluentAutogen.tt new file mode 100644 index 0000000..b0e7bbf --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Routes/ControllerRouteFluentAutogen.tt @@ -0,0 +1,94 @@ +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> +namespace Kit.Helpers.Routes +{ + using System; + using System.Linq.Expressions; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Mvc; + + public partial class ControllerRouteFluent : IEndPointFluent> + where TController : Controller + { + public delegate TResult GenericResult0Delegate(); + public delegate Task GenericResult0DelegateAsync(); + +<# + string _paramsT1 = "TResult"; + string _paramsD1 = string.Empty; + for (int i = 1; i <= 5; i++) + { + string paramName = "TInput" + i.ToString(); + + _paramsT1 = _paramsT1 + ", " + paramName; + _paramsD1 = _paramsD1 + (_paramsD1 != string.Empty ? ", " : string.Empty) + paramName + " input" + i.ToString(); +#> + public delegate TResult GenericResult<#= i #>Delegate<<#= _paramsT1 #>>(<#= _paramsD1 #>); + public delegate Task GenericResult<#= i #>DelegateAsync<<#= _paramsT1 #>>(<#= _paramsD1 #>); + +<# + } +#> + +<# + var actionTypes = new List> + { + new ("Generic", "TResult", true), + new ("Json", "JsonResult", false), + new ("Empty", "EmptyResult", false), + new ("Content", "ContentResult", false), + new ("View", "ViewResult", false), + new ("PartialView", "PartialViewResult", false), + new ("Action", "IActionResult", false), + }; + + foreach (var actionType in actionTypes) + { + bool withResultInGeneric = actionType.Item3; + + var items = new List>(); + + items.Add(new Tuple(actionType.Item1, 0, withResultInGeneric ? "<" + actionType.Item2 + ">" : string.Empty, actionType.Item2)); + + string _paramsT = withResultInGeneric ? actionType.Item2 : string.Empty; + string _paramsDelegate = actionType.Item2; + + for (int i = 1; i <= 5; i++) + { + string genericString = "TInput" + i.ToString(); + + _paramsT += (_paramsT == string.Empty ? string.Empty : ", ") + genericString; + _paramsDelegate += ", " + genericString; + + items.Add(new Tuple(actionType.Item1, i, "<" + _paramsT + ">", _paramsDelegate)); + } +#> + #region <#= actionType.Item2 #> Actions + +<# + foreach (var item in items) + { +#> + public ControllerRouteFluent <#= item.Item1 #>Action<#= item.Item3 #>(string url, ExpressionDelegate<<#= item.Item4 #>>>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + public ControllerRouteFluent <#= item.Item1 #>ActionAsync<#= item.Item3 #>(string url, ExpressionDelegateAsync<<#= item.Item4 #>>>> actionSelector) + => this.AddRouteLambda(url, actionSelector); + + + +<# + } +#> + #endregion + + +<# + } +#> + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Routes/IRouteFluent.cs b/LibCommon/Kit.Core.Helpers/Routes/IRouteFluent.cs new file mode 100644 index 0000000..b9032fc --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Routes/IRouteFluent.cs @@ -0,0 +1,13 @@ +namespace Kit.Helpers.Routes +{ + public interface IEndPointFluent + { + TRouteFluent NeedHttpGet(bool required = true); + TRouteFluent NeedHttpPost(bool required = true); + TRouteFluent NeedSecurityKey(string securityKeyName); + TRouteFluent WithDefaults(object defaults); + TRouteFluent WithConstraints(object constraints); + TRouteFluent WithNamespaces(string[] namespaces); + TRouteFluent WithDataTokens(object dataTokens); + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Routes/RouteFluent.cs b/LibCommon/Kit.Core.Helpers/Routes/RouteFluent.cs new file mode 100644 index 0000000..7035e38 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Routes/RouteFluent.cs @@ -0,0 +1,141 @@ +namespace Kit.Helpers.Routes +{ + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Routing; + using Microsoft.AspNetCore.Routing.Constraints; + + public class BodyConstraint : IRouteConstraint, IParameterPolicy + { + private readonly bool _required; + public BodyConstraint(bool required) + { + _required = required; + } + public bool Match(HttpContext? httpContext, IRouter? route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection) + { + bool hasBody = string.IsNullOrWhiteSpace(httpContext?.Request.ContentType) == false; + return _required == hasBody; + } + } + + public class RouteFluent : IEndPointFluent + { + public Route Route { get; set; } + + public RouteFluent NeedHttpGet(bool required = true) + { + this.Route.Constraints.SetByKey("httpMethod", new HttpMethodRouteConstraint("GET")); + return this; + } + + public string GetMethod() + { + if (IsMethodPost()) return "POST"; + if (IsMethodPut()) return "PUT"; + if (IsMethodDelete()) return "DELETE"; + return "GET"; + } + + public bool IsMethodNotSpecified() + { + return this.Route.Constraints.ContainsKey("httpMethod") == false; + } + + public bool IsMethodGet() + { + if (this.Route.Constraints.ContainsKey("httpMethod") == false) return false; + return (this.Route.Constraints.GetByKey("httpMethod") as HttpMethodRouteConstraint)!.AllowedMethods.Contains("GET"); + } + + public RouteFluent NeedHttpPost(bool required = true) + { + this.Route.Constraints.SetByKey("httpMethod", new HttpMethodRouteConstraint("POST")); + return this; + } + + public bool IsMethodPost() + { + if (this.Route.Constraints.ContainsKey("httpMethod") == false) return false; + return (this.Route.Constraints.GetByKey("httpMethod") as HttpMethodRouteConstraint)!.AllowedMethods.Contains("POST"); + } + + public RouteFluent NeedHttpPut(bool required = true) + { + this.Route.Constraints.SetByKey("httpMethod", new HttpMethodRouteConstraint("PUT")); + return this; + } + + public bool IsMethodPut() + { + if (this.Route.Constraints.ContainsKey("httpMethod") == false) return false; + return (this.Route.Constraints.GetByKey("httpMethod") as HttpMethodRouteConstraint)!.AllowedMethods.Contains("PUT"); + } + + public RouteFluent NeedHttpDelete(bool required = true) + { + this.Route.Constraints.SetByKey("httpMethod", new HttpMethodRouteConstraint("DELETE")); + return this; + } + + public bool IsMethodDelete() + { + if (this.Route.Constraints.ContainsKey("httpMethod") == false) return false; + return (this.Route.Constraints.GetByKey("httpMethod") as HttpMethodRouteConstraint)!.AllowedMethods.Contains("DELETE"); + } + + public RouteFluent NeedSecurityKey(string securityKeyName) + { + this.Route.DataTokens["APP_SecurityKeyName"] = securityKeyName; + return this; + } + + public RouteFluent WithBody(bool required = true) + { + this.Route.Constraints.SetByKey("withBody", new BodyConstraint(required)); + return this; + } + + public RouteFluent ForbidForReadonly(bool value = true) + { + this.Route.DataTokens["forbidForReadonly"] = value; + return this; + } + + public RouteFluent WithDefaults(object defaults) + { + if (defaults == null) return this; + foreach (var pair in new RouteValueDictionary(defaults)) + { + this.Route.Defaults.Add(pair.Key, pair.Value); + } + return this; + } + + public RouteFluent WithConstraints(object constraints) + { + if (constraints == null) return this; + foreach (var pair in new RouteValueDictionary(constraints)) + { + this.Route.Constraints.SetByKey(pair.Key, (IRouteConstraint)pair.Value); + } + return this; + } + + public RouteFluent WithNamespaces(string[] namespaces) + { + if (namespaces == null) return this; + this.Route.DataTokens["Namespaces"] = namespaces; + return this; + } + + public RouteFluent WithDataTokens(object dataTokens) + { + if (dataTokens == null) return this; + foreach (var pair in new RouteValueDictionary(dataTokens)) + { + this.Route.DataTokens[pair.Key] = pair.Value; + } + return this; + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Routes/RouteHelpers.cs b/LibCommon/Kit.Core.Helpers/Routes/RouteHelpers.cs new file mode 100644 index 0000000..74cfac0 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Routes/RouteHelpers.cs @@ -0,0 +1,133 @@ +namespace Kit.Helpers.Routes +{ + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Routing; + using Microsoft.Extensions.DependencyInjection; + using System; + using System.Reflection; + + public class ControllerMethod + { + public string Name { get; } + public string Controller { get; } + public string RouteTemplate { get; } + public string Method => _route.GetMethod(); + public MethodInfo MethodInfo { get; } + public RouteValueDictionary RouteDataTokens + { + get + { + var result = new RouteValueDictionary(); + _route.Route.DataTokens?.ForEach(x => result.Add(x.Key, x.Value)); + return result; + } + } + private RouteFluent _route { get; } + public ControllerMethod(string name, MethodInfo methodInfo, RouteFluent route) + { + MethodInfo = methodInfo; + _route = route; + + Name = name; + Controller = _route.Route?.Defaults["controller"]?.ToString() ?? string.Empty; + RouteTemplate = _route.Route?.RouteTemplate ?? string.Empty; + } + } + + public static class RouteHelper_Extensions + { + public static List ControllerMethods = new List(); + + private static IInlineConstraintResolver CreateInlineConstraintResolver(IServiceProvider serviceProvider) + { + var inlineConstraintResolver = serviceProvider + .GetRequiredService(); + + var parameterPolicyFactory = serviceProvider + .GetRequiredService(); + + // This inline constraint resolver will return a null constraint for non-IRouteConstraint + // parameter policies so Route does not error + return new BackCompatInlineConstraintResolver(inlineConstraintResolver, parameterPolicyFactory); + } + + internal class NullRouteConstraint : IRouteConstraint + { + public static readonly NullRouteConstraint Instance = new NullRouteConstraint(); + + private NullRouteConstraint() + { + } + + public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection) + { + return true; + } + } + private class BackCompatInlineConstraintResolver : IInlineConstraintResolver + { + private readonly IInlineConstraintResolver _inner; + private readonly ParameterPolicyFactory _parameterPolicyFactory; + + public BackCompatInlineConstraintResolver(IInlineConstraintResolver inner, ParameterPolicyFactory parameterPolicyFactory) + { + _inner = inner; + _parameterPolicyFactory = parameterPolicyFactory; + } + + public IRouteConstraint ResolveConstraint(string inlineConstraint) + { + var routeConstraint = _inner.ResolveConstraint(inlineConstraint); + if (routeConstraint != null) + { + return routeConstraint; + } + + var parameterPolicy = _parameterPolicyFactory.Create(null, inlineConstraint); + if (parameterPolicy != null) + { + // Logic inside Route will skip adding NullRouteConstraint + return NullRouteConstraint.Instance; + } + + return null; + } + } + + + + + public static RouteFluent AddRoute(this IRouteBuilder routeBuilder, string name, string url, string controller, string action, MethodInfo methodInfo) + { + var inline = CreateInlineConstraintResolver(routeBuilder.ServiceProvider); + var route = new Route(routeBuilder.DefaultHandler, name, url, new RouteValueDictionary(new { controller = controller, action = action }), null, null, inline); + routeBuilder.Routes.Add(route); + var result = new RouteFluent { Route = route }; + ControllerMethods.Add(new ControllerMethod(name, methodInfo, result)); + return result; + } + + public static ControllerBaseRouteFluent AddControllerBase(this IRouteBuilder routeTable) + where TController : ControllerBase + { + return new ControllerBaseRouteFluent(routeTable); + } + + public static ControllerRouteFluent AddController(this IRouteBuilder routeTable) + where TController : Controller + { + return new ControllerRouteFluent(routeTable); + } + + public static RouteBase GetBaseRoute(this ControllerContext context) + { + return context.GetBaseRoute(); + } + + public static bool GetForbidForReadonly(this RouteData route) + { + return (route.DataTokens["forbidForReadonly"] as bool?) ?? false; + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Rsa/DigitalSignature.cs b/LibCommon/Kit.Core.Helpers/Rsa/DigitalSignature.cs new file mode 100644 index 0000000..58da47b --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Rsa/DigitalSignature.cs @@ -0,0 +1,87 @@ +namespace Kit.Helpers.Rsa +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + using System.Threading.Tasks; + + public class DigitalSignature + { + private string _publicKey; + private string _privateKey; + private int _dwKeySize; + + public string PublicKey { get { return _publicKey; } } + public string PrivateKey { get { return _privateKey; } } + + public DigitalSignature(int dwKeySize) + { + _dwKeySize = dwKeySize; + } + + public void AssignKey(string publicKey, string privateKey) + { + _publicKey = publicKey; + _privateKey = privateKey; + } + + public void AssignNewKey() + { + using (var rsa = new RSACryptoServiceProvider(_dwKeySize)) + { + rsa.PersistKeyInCsp = false; + _publicKey = rsa.ToXmlString(false); + _privateKey = rsa.ToXmlString(true); + } + } + public byte[] SignData(string data) + { + return SignData(Encoding.UTF8.GetBytes(data)); + } + + public byte[] SignData(byte[] data) + { + using (var rsa = new RSACryptoServiceProvider(_dwKeySize)) + { + byte[] hash; + using (SHA256 sha256 = SHA256.Create()) + { + hash = sha256.ComputeHash(data); + } + + rsa.PersistKeyInCsp = false; + rsa.FromXmlString(_privateKey); + + var rsaFormatter = new RSAPKCS1SignatureFormatter(rsa); + rsaFormatter.SetHashAlgorithm("SHA256"); + return rsaFormatter.CreateSignature(hash); + } + } + + public bool VerifySignature(string data, byte[] signature) + { + + return VerifySignature(Encoding.UTF8.GetBytes(data), signature); + } + + public bool VerifySignature(byte[] data, byte[] signature) + { + using (var rsa = new RSACryptoServiceProvider(_dwKeySize)) + { + byte[] hash; + using (SHA256 sha256 = SHA256.Create()) + { + hash = sha256.ComputeHash(data); + } + + rsa.FromXmlString(_publicKey); + var rsaDeformatter = new RSAPKCS1SignatureDeformatter(rsa); + rsaDeformatter.SetHashAlgorithm("SHA256"); + return rsaDeformatter.VerifySignature(hash, signature); + } + } + + } +} diff --git a/LibCommon/Kit.Core.Helpers/Rsa/RSAService.cs b/LibCommon/Kit.Core.Helpers/Rsa/RSAService.cs new file mode 100644 index 0000000..17661c4 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Rsa/RSAService.cs @@ -0,0 +1,190 @@ +namespace Kit.Helpers +{ + using System; + using System.Linq; + using System.IO; + using System.Security.Cryptography; + using System.IO.Compression; + + public static class RSAService + { + public static byte[] RSAEncrypt(this byte[] dataToEncrypt, string privateKey, bool DoOAEPPadding = false) + { + using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(1024)) + { + rsa.FromXmlString(privateKey); + + using (var fstreamOut = new MemoryStream()) + { + int i = 0; + int sizebuf = 64; + byte[] buf = new byte[sizebuf]; + do + { + buf = dataToEncrypt.Skip(i * sizebuf).Take(sizebuf).ToArray(); + if (buf.Length == 0) break; + byte[] encrypted = rsa.Encrypt(buf, DoOAEPPadding); + fstreamOut.Write(encrypted, 0, encrypted.Length); + i++; + } while (buf.Length == sizebuf); + + fstreamOut.Position = 0; + return fstreamOut.ReadAllBytes(); + } + } + } + + public static byte[] RSADecrypt(this byte[] dataToDecrypt, string publickey, bool DoOAEPPadding = false) + { + using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(1024)) + { + rsa.FromXmlString(publickey); + + using (var fstreamOut = new MemoryStream()) + { + int i = 0; + int sizebuf = 128; + byte[] buf = new byte[sizebuf]; + do + { + buf = dataToDecrypt.Skip(i * sizebuf).Take(sizebuf).ToArray(); + if (buf.Length == 0) break; + byte[] encrypted = rsa.Decrypt(buf, DoOAEPPadding); + fstreamOut.Write(encrypted, 0, encrypted.Length); + i++; + } while (buf.Length == sizebuf); + + fstreamOut.Position = 0; + return fstreamOut.ReadAllBytes(); + } + } + } + + public static byte[] RSAGetSignature(this byte[] data, string privedkey) + { + + using (RSACryptoServiceProvider rsaCSP = new RSACryptoServiceProvider()) + { + rsaCSP.FromXmlString(privedkey); + SHA256Managed hash = new SHA256Managed(); + byte[] hashedData; + + hashedData = hash.ComputeHash(data); + return rsaCSP.SignHash(hashedData, CryptoConfig.MapNameToOID("SHA256")); + } + } + + public static bool RSAVerifySignature(this byte[] signedData, string publickey, byte[] signature) + { + using (RSACryptoServiceProvider rsaCSP = new RSACryptoServiceProvider()) + { + rsaCSP.FromXmlString(publickey); + SHA256Managed hash = new SHA256Managed(); + byte[] hashedData; + + bool dataOK = rsaCSP.VerifyData(signedData, CryptoConfig.MapNameToOID("SHA256"), signature); + hashedData = hash.ComputeHash(signedData); + return rsaCSP.VerifyHash(hashedData, CryptoConfig.MapNameToOID("SHA256"), signature); + } + } + public static Stream GetPackedSignature(this Stream stream, string privedkey) + { + return stream.ReadAllBytes().GetPackedSignature(privedkey); + } + + public static Stream GetPackedSignature(this byte[] data, string privedkey) + { + byte[] signature = data.RSAGetSignature(privedkey); + Stream packed = new MemoryStream(); + + using (var zipObj = new ZipArchive(packed, ZipArchiveMode.Create,true)) + { + ZipArchiveEntry entryData = zipObj.CreateEntry("file.dat"); + using (var entryStream = entryData.Open()) + { + entryStream.Write(data, 0, data.Length); + entryStream.Flush(); + } + + ZipArchiveEntry entrySignature = zipObj.CreateEntry("signature.key"); + using (var entryStream = entrySignature.Open()) + { + entryStream.Write(signature, 0, signature.Length); + entryStream.Flush(); + } + + } + + return packed; + } + + public static bool VerifyPackedSignature(this byte[] arhiveData, string publickey) + { + using (ZipArchive archive = new ZipArchive(new MemoryStream(arhiveData), ZipArchiveMode.Read)) + { + //если нет файла подписи и файла данных (нарушен формат пакета) то проверка отрицательная + if ( + !archive.Entries.Any(x => x.FullName.EndsWith("signature.key")) + || + !archive.Entries.Any(x => x.FullName.EndsWith("file.dat")) + ) return false; + + byte[] signature = new byte[] { }; + byte[] data = new byte[] { }; + + foreach (ZipArchiveEntry entry in archive.Entries) + { + if (entry.FullName.EndsWith("signature.key", StringComparison.OrdinalIgnoreCase)) + { + signature = entry.Open().ReadAllBytes(); + } + + if (entry.FullName.EndsWith("file.dat", StringComparison.OrdinalIgnoreCase)) + { + data = entry.Open().ReadAllBytes(); + } + } + + if (signature.Length == 0 || data.Length == 0) return false; + + return RSAVerifySignature(data, publickey, signature); + } + + } + + public static byte[] ReadPackedWithSignature(this byte[] arhiveData, string publickey) + { + return new MemoryStream(arhiveData).ReadPackedWithSignature(publickey); + } + public static byte[] ReadPackedWithSignature(this Stream arhiveStream, string publickey) + { + using (ZipArchive archive = new ZipArchive(arhiveStream, ZipArchiveMode.Read)) + { + byte[] signature = new byte[] { }; + byte[] data = new byte[] { }; + + foreach (ZipArchiveEntry entry in archive.Entries) + { + if (entry.FullName.EndsWith("signature.key", StringComparison.OrdinalIgnoreCase)) + { + signature = entry.Open().ReadAllBytes(); + } + + if (entry.FullName.EndsWith("file.dat", StringComparison.OrdinalIgnoreCase)) + { + data = entry.Open().ReadAllBytes(); + } + } + + if (signature.Length == 0 || data.Length == 0) return null; + + if(RSAVerifySignature(data, publickey, signature)) + { + return data; + } + } + return null; + } + } +} + diff --git a/LibCommon/Kit.Core.Helpers/Service/FileTypeKeys.cs b/LibCommon/Kit.Core.Helpers/Service/FileTypeKeys.cs new file mode 100644 index 0000000..822e808 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Service/FileTypeKeys.cs @@ -0,0 +1,10 @@ +namespace Kit.Helpers.Service; + +public static class FileTypeKeys +{ + public const string SQLiteAll = "sqlite_all"; + public const string SQLiteFileService = "sqlite_fileservice"; + + + public const string XmlAll = "xml_all"; +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Service/HardwareInfoService.cs b/LibCommon/Kit.Core.Helpers/Service/HardwareInfoService.cs new file mode 100644 index 0000000..39f52aa --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Service/HardwareInfoService.cs @@ -0,0 +1,181 @@ +using System.Diagnostics; +using System.Management; + +using System.Net.NetworkInformation; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; + +namespace Kit.Helpers +{ + public class OperatingSystemInfo + { + public string OSVersion { get; set; } = string.Empty; + public string BitOperatingSystem { get; set; } = string.Empty; + public string BitProcess { get; set; } = string.Empty; + } + + public interface IHardwareInfoProvider + { + string GetProcessorId(); + string GetBoardSerial(); + string GetMacAddress(); + string GetMachineName(); + } + + internal abstract class HardwareInfoProviderBase : IHardwareInfoProvider + { + public abstract string GetProcessorId(); + public abstract string GetBoardSerial(); + public string GetMacAddress() + { + try + { + var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces() + .Where(n => n.OperationalStatus == OperationalStatus.Up && n.NetworkInterfaceType != NetworkInterfaceType.Loopback) + .FirstOrDefault(); + + if (networkInterfaces != null) + { + return networkInterfaces.GetPhysicalAddress().ToString(); + } + return "N/A"; + } + catch (Exception ex) + { + return $"Error: {ex.Message}"; + } + } + + public string GetMachineName() => Environment.MachineName; + } + +#pragma warning disable CA1416 // Validate platform compatibility + internal class HardwareInfoProviderWindows : HardwareInfoProviderBase, IHardwareInfoProvider + { + private static string _scope = "root\\CIMV2"; + private static string GetComponent(string scope, string hwClass, string syntax) + { + ManagementObjectSearcher mos = new ManagementObjectSearcher(scope, "SELECT * FROM " + hwClass); + + string result = ""; + + foreach (ManagementObject mObj in mos.Get()) + { + result = Convert.ToString(mObj[syntax]) ?? string.Empty; + } + + return result; + } + + public override string GetProcessorId() => GetComponent(_scope, "Win32_processor", "ProcessorID"); + public override string GetBoardSerial() => GetComponent(_scope, "Win32_BaseBoard", "SerialNumber"); + } + + internal class HardwareInfoProviderLinux : HardwareInfoProviderBase, IHardwareInfoProvider + { + private string ParseCpuIdOutput(string output) + { + // Пример парсинга вывода cpuid + // Ищем сигнатуру процессора (eax=1) + var lines = output.Split('\n'); + foreach (var line in lines) + { + if (line.Contains("0x00000001 0x00:")) + { + // Пример строки: "0x00000001 0x00: eax=0x000306c3 ebx=0x01000800 ecx=0x7ffafbff edx=0xbfebfbff" + var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries); + string eax = parts[2].Split('=')[1]; // Например, "0x000306c3" + string ebx = parts[3].Split('=')[1]; // Например, "0x01000800" + string edx = parts[5].Split('=')[1]; // Например, "0xbfebfbff" + + // Комбинируем как уникальный идентификатор + //return $"{eax}-{ebx}-{edx}"; + return $"{edx.Split('x').ElementAt(1)}{eax.Split('x').ElementAt(1)}".ToUpper(); + } + } + + return "Не удалось найти данные CPUID"; + } + public override string GetProcessorId() + { + try + { + // Настройка процесса для вызова cpuid + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "cpuid", + Arguments = "-r", // Сырой вывод CPUID + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true + } + }; + + // Запуск процесса + process.Start(); + string output = process.StandardOutput.ReadToEnd(); + process.WaitForExit(); + + // Парсинг вывода для получения нужной информации + return ParseCpuIdOutput(output); + } + catch (Exception ex) + { + return $"Ошибка при получении Hardware ID: {ex.Message}"; + } + } + public override string GetBoardSerial() => string.Empty; + } + + internal class HardwareInfoProviderDebug : HardwareInfoProviderBase, IHardwareInfoProvider + { + public override string GetProcessorId() => "FEDCBA9876543210"; + public override string GetBoardSerial() => "Debug board serial number"; + } +#pragma warning restore CA1416 // Validate platform compatibility + + public interface IHardwareInfoService + { + IHardwareInfoProvider Provider { get; } + OperatingSystemInfo GetOperatingSystemInfo(); + string GetHardwareId(); + } + + internal class HardwareInfoService : IHardwareInfoService + { + public OperatingSystemInfo GetOperatingSystemInfo() => new OperatingSystemInfo + { + BitOperatingSystem = Environment.Is64BitProcess ? "x64" : "x32", + BitProcess = Environment.Is64BitOperatingSystem ? "x64" : "x32", + OSVersion = RuntimeInformation.OSDescription + }; + + public IHardwareInfoProvider Provider { get; private set; } + public HardwareInfoService(IHardwareInfoProvider infoProvider) + { + Provider = infoProvider; + } + + public string GetHardwareId() + { + string processorId = Provider.GetProcessorId(); + string baseBoardId = Provider.GetBoardSerial(); + string macAddress = Provider.GetMacAddress(); + string machineName = Provider.GetMachineName(); + + return CalculateHash($"{processorId}{baseBoardId}{macAddress}{machineName}"); + } + + private string CalculateHash(string input) + { + using (var algorithm = SHA512.Create()) + { + var hashedBytes = algorithm.ComputeHash(Encoding.UTF8.GetBytes(input)); + return BitConverter.ToString(hashedBytes).Replace("-", "").ToLower(); + } + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Service/LockService.cs b/LibCommon/Kit.Core.Helpers/Service/LockService.cs new file mode 100644 index 0000000..87f041f --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Service/LockService.cs @@ -0,0 +1,79 @@ +namespace Kit.Helpers +{ + public interface ILockService + { + TResult Lock(TKey lockKey, Func action); + void Lock(TKey lockKey, Action action); + } + public class LockService : ILockService + { + private object _lockObjectLock = new object(); + private IDictionary _lockObjects = new Dictionary(); + + /// Блокировка операции по ключу + /// Ключ для блокировки + /// Выполняемая операция + public TResult Lock(TKey lockKey, Func action) + { + try + { + lock (GetLockObject(lockKey)) + { + return action(); + } + } + finally + { + RemoveLockObject(lockKey); + } + } + + /// Блокировка операции по ключу + /// Ключ для блокировки + /// Выполняемая операция + public void Lock(TKey lockKey, Action action) + { + try + { + lock (GetLockObject(lockKey)) + { + action(); + } + } + finally + { + RemoveLockObject(lockKey); + } + } + + /// Получение объекта для блокировки по ключу + /// Ключ для блокировки + /// Объект блокировки, соответствующий ключу + private object GetLockObject(TKey lockKey) + { + lock (_lockObjectLock) + { + if (_lockObjects.ContainsKey(lockKey) == false) + { + _lockObjects.Add(lockKey, new object()); + } + + return _lockObjects[lockKey]; + } + } + + /// Удаление объекта блокировки, соответствующего ключу + /// Ключ для блокировки + private void RemoveLockObject(TKey lockKey) + { + /// дожидаемся освобождения объекта + lock (this.GetLockObject(lockKey)) { } + + /// удаляем объект блокировки из очереди + lock (_lockObjectLock) + { + _lockObjects.Remove(lockKey); + } + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Service/Sqlite/All/UpdateSQLiteAllToVersion1.cs b/LibCommon/Kit.Core.Helpers/Service/Sqlite/All/UpdateSQLiteAllToVersion1.cs new file mode 100644 index 0000000..4cbd3be --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Service/Sqlite/All/UpdateSQLiteAllToVersion1.cs @@ -0,0 +1,24 @@ +using Kit.Helpers.Repository; + +namespace Kit.Helpers.Service +{ + public class UpdateSQLiteAllToVersion1 : SQLiteVersionUpdateItem + { + public override string FileTypeKey => FileTypeKeys.SQLiteAll; + public override int OldVersion => 0; + public override int NewVersion => 1; + + private const string _createTableProcedures = @" +create table procedures ( + procedure_name text not null primary key, + params text not null, + text text not null +); +"; + + public override void Execute(FileContentSqlite file) + { + file.ConnectionString.PrepareExecute(_createTableProcedures).AsSqlText().WithStrictSyntax().ExecuteNonQuery(); + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Service/Sqlite/SQLiteVersionService.cs b/LibCommon/Kit.Core.Helpers/Service/Sqlite/SQLiteVersionService.cs new file mode 100644 index 0000000..5d21abe --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Service/Sqlite/SQLiteVersionService.cs @@ -0,0 +1,100 @@ +using Microsoft.Extensions.Logging; +using Kit.Helpers.Repository; + +namespace Kit.Helpers.Service +{ + public interface ISQLiteVersionService + { + void AppendType(FileContentSqlite fileContentSqlite, string fileTypeKey); + bool UpdateByTypes(FileContentSqlite fileContentSqlite); + + int GetRowVersion(FileContentSqlite fileContentSqlite); + void SetRowVersion(FileContentSqlite fileContentSqlite, int rowVersion); + } + + public class SQLiteVersionService : ISQLiteVersionService + { + private ISQLiteVersionRepository _fileVersionRepository; + private ISQLiteGlobalVarRepository _sqLiteGlobalVarRepository; + private IDictionary> _updateItems; + private IDictionary _latestVersions; + public SQLiteVersionService( + ISQLiteVersionRepository fileVersionRepository, + ISQLiteGlobalVarRepository sqLiteGlobalVarRepository, + IEnumerable updateItems + ) + { + _fileVersionRepository = fileVersionRepository; + _sqLiteGlobalVarRepository = sqLiteGlobalVarRepository; + _updateItems = updateItems?.GroupBy(x => x.FileTypeKey ?? string.Empty).ToDictionary(x => x.Key, x => x.ToList()) ?? new Dictionary>(); + _latestVersions = _updateItems.ToDictionary(x => x.Key, x => x.Value.Max(y => y.NewVersion)); + } + + public void AppendType(FileContentSqlite fileContentSqlite, string fileTypeKey) + { + SQLiteFileVersionInfo? sqlFileVersionInfo = _fileVersionRepository.Select(fileContentSqlite).SingleOrDefault(x => x.Type == fileTypeKey); + if (sqlFileVersionInfo == null) + { + _fileVersionRepository.Set(fileContentSqlite, new SQLiteFileVersionInfo { Type = fileTypeKey, Version = 0 }); + } + } + + public bool UpdateByTypes(FileContentSqlite fileContentSqlite) + { + IEnumerable fileVersionInfos = _fileVersionRepository.Select(fileContentSqlite); + Check.IsNotNullOrEmpty(fileVersionInfos, "В файле не указана информация о версиях компонентов"); + + bool isUpdated = false; + + fileVersionInfos.ForEach(fileVersionInfo => + { + /// Если информации по типу нет - выходим + if (_latestVersions.ContainsKey(fileVersionInfo.Type) == false) + { + return; + } + /// Если файл по этому типу уже актуальный - выходим + if (_latestVersions[fileVersionInfo.Type] == fileVersionInfo.Version) + { + return; + } + + /// Элементы для обновления по типу + IEnumerable? updateItems = _updateItems[fileVersionInfo.Type]; + + SQLiteFileVersionInfo fileVersionInfoIter = fileVersionInfo; + ISQLiteVersionUpdateItem? updateItem = null; + + /// Обновляемся до актуальной версии по типу + do + { + updateItem = updateItems.FirstOrDefault(x => x.OldVersion == fileVersionInfoIter.Version); + if (updateItem != null) + { + updateItem.Execute(fileContentSqlite); + fileVersionInfoIter = new SQLiteFileVersionInfo { Type = fileVersionInfo.Type, Version = updateItem.NewVersion }; + _fileVersionRepository.Set(fileContentSqlite, fileVersionInfoIter); + isUpdated = true; + } + } + while (updateItem != null); + }); + +#if DEBUG + fileVersionInfos = _fileVersionRepository.Select(fileContentSqlite); +#endif + + return isUpdated; + } + + public int GetRowVersion(FileContentSqlite fileContentSqlite) + { + return _sqLiteGlobalVarRepository.Get(fileContentSqlite, "row_version").TryParseToInt32(0); + } + + public void SetRowVersion(FileContentSqlite fileContentSqlite, int rowVersion) + { + _sqLiteGlobalVarRepository.Set(fileContentSqlite, "row_version", rowVersion.ToString()); + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Service/Sqlite/SQLiteVersionUpdateItem.cs b/LibCommon/Kit.Core.Helpers/Service/Sqlite/SQLiteVersionUpdateItem.cs new file mode 100644 index 0000000..c1744f0 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Service/Sqlite/SQLiteVersionUpdateItem.cs @@ -0,0 +1,22 @@ +using Kit.Helpers.Repository; + +namespace Kit.Helpers.Service +{ + public interface ISQLiteVersionUpdateItem + { + void Execute(FileContentSqlite file); + + string FileTypeKey { get; } + int OldVersion { get; } + int NewVersion { get; } + } + + public abstract class SQLiteVersionUpdateItem : ISQLiteVersionUpdateItem + { + public abstract string FileTypeKey { get; } + public abstract int OldVersion { get; } + public abstract int NewVersion { get; } + + public abstract void Execute(FileContentSqlite file); + } +} diff --git a/LibCommon/Kit.Core.Helpers/Service/Xml/XmlVersionService.cs b/LibCommon/Kit.Core.Helpers/Service/Xml/XmlVersionService.cs new file mode 100644 index 0000000..ac18b3a --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Service/Xml/XmlVersionService.cs @@ -0,0 +1,81 @@ +using Kit.Helpers.Repository.Xml; +using System.Xml; + +namespace Kit.Helpers.Service +{ + public interface IXmlVersionService + { + string? GetFileType(XmlDocument document); + void SetFileType(XmlDocument document, string fileTypeKey, int version = 0, bool rewriteIfExist = false); + void Init(XmlDocument document, string fileTypeKey); + bool Update(XmlDocument document); + } + + public class XmlVersionService : IXmlVersionService + { + private IXmlFileVersionRepository _fileVersionRepository; + private IDictionary> _updateItems; + public XmlVersionService(IXmlFileVersionRepository fileVersionRepository, IEnumerable updateItems) + { + _fileVersionRepository = fileVersionRepository; + _updateItems = updateItems?.GroupBy(x => x.FileTypeKey ?? string.Empty).ToDictionary(x => x.Key, x => x.ToList()) ?? new Dictionary>(); + } + + public string? GetFileType(XmlDocument document) + { + return _fileVersionRepository.Get(document)?.Type; + } + + public void SetFileType(XmlDocument document, string fileTypeKey, int version = 0, bool rewriteIfExist = true) + { + var fileVersion = _fileVersionRepository.Get(document); + + if (rewriteIfExist || fileVersion == null) + { + _fileVersionRepository.Set(document, new XmlFileVersionInfo { Type = fileTypeKey, Version = version }); + } + } + + private bool ModifyFile(XmlDocument document, XmlFileVersionInfo fileVersionInfo) + { + IEnumerable? updateItems = _updateItems[fileVersionInfo.Type]; + if (updateItems.IsNullOrEmpty()) return false; + + XmlFileVersionInfo fileVersionInfoIter = fileVersionInfo; + IXmlVersionUpdateItem? updateItem = null; + + bool isUpdated = false; + + do + { + updateItem = updateItems.FirstOrDefault(x => x.OldVersion == fileVersionInfoIter.Version); + if (updateItem != null) + { + updateItem.Execute(document); + fileVersionInfoIter = new XmlFileVersionInfo { Type = fileVersionInfo.Type, Version = updateItem.NewVersion }; + _fileVersionRepository.Set(document, fileVersionInfoIter); + isUpdated = true; + } + } + while (updateItem != null); + + return isUpdated; + } + + public void Init(XmlDocument document, string fileTypeKey) + { + ModifyFile(document, new XmlFileVersionInfo { Type = fileTypeKey, Version = 0 }); + } + + public bool Update(XmlDocument document) + { + XmlFileVersionInfo? fileVersionInfo = _fileVersionRepository.Get(document); + if (fileVersionInfo == null) throw new Exception("В файле не указана информация о версии"); + if (string.IsNullOrWhiteSpace(fileVersionInfo.Type)) throw new Exception("В версии файла не указана информация о типе файла"); + + if (_updateItems.ContainsKey(fileVersionInfo.Type) == false) throw new Exception($"В системе отсутствует информация о методах обновления версии файла указанного типа"); + + return ModifyFile(document, fileVersionInfo); + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Service/Xml/XmlVersionUpdateItem.cs b/LibCommon/Kit.Core.Helpers/Service/Xml/XmlVersionUpdateItem.cs new file mode 100644 index 0000000..728d04e --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Service/Xml/XmlVersionUpdateItem.cs @@ -0,0 +1,22 @@ +using System.Xml; + +namespace Kit.Helpers.Service +{ + public interface IXmlVersionUpdateItem + { + void Execute(XmlDocument document); + + string FileTypeKey { get; } + int OldVersion { get; } + int NewVersion { get; } + } + + public abstract class XmlVersionUpdateItem : IXmlVersionUpdateItem + { + public abstract string FileTypeKey { get; } + public abstract int OldVersion { get; } + public abstract int NewVersion { get; } + + public abstract void Execute(XmlDocument document); + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/StaticKeysHelper.cs b/LibCommon/Kit.Core.Helpers/StaticKeysHelper.cs new file mode 100644 index 0000000..369f996 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/StaticKeysHelper.cs @@ -0,0 +1,19 @@ +using System.Reflection; + +namespace Kit.Helpers +{ + public static class StaticKeysHelper + { + /// Получить все константные значения из типа + /// Тип, из которого нуж + /// + public static T[] GetAllConstValues(Type type) + { + return type + .GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy) + .Where(fi => fi.IsLiteral && !fi.IsInitOnly && fi.FieldType == typeof(T)) + .Select(x => (T)x.GetRawConstantValue()) + .ToArray(); + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/TreeHelper.cs b/LibCommon/Kit.Core.Helpers/TreeHelper.cs new file mode 100644 index 0000000..35c5695 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/TreeHelper.cs @@ -0,0 +1,66 @@ +namespace Kit.Helpers; + +public class TreeNode +{ + public TKey Id { get; set; } + public TKey ParentId { get; set; } + public TEntity Item { get; set; } + public int Layer { get; set; } + public List> ChildNodes { get; set; } + public bool HasContent { get; set; } + public TreeNode() + { + ChildNodes = new List>(); + } +} + +public static class TreeHelper +{ + /// Преобразование дерева с родительскими указателями в дерево с дочерними указателями + public static IEnumerable> ConvertToTreeNodes(this IEnumerable entities, Func getId, Func getParentId, Func idEquals, TKey parentIdEmpty) + { + IEnumerable> treeNodes = entities.Select(x => new TreeNode + { + Id = getId(x), + ParentId = getParentId(x), + Item = x, + }).ToList(); + + treeNodes.ForEach(treeNode => + { + treeNode.ChildNodes = treeNodes.Where(x => idEquals(treeNode.Id, x.ParentId)).ToList(); + }); + + return treeNodes.Where(x => x.ParentId.Equals(parentIdEmpty)).ToList(); + } + + public static List> FlattenTree(this IEnumerable> nodes, Func, TOrderKey> getSortKey, int layer = 0) + { + var result = new List>(); + + if (nodes.IsNullOrEmpty()) return result; + + foreach (var node in nodes.OrderBy(getSortKey)) + { + node.Layer = layer; + + result.Add(node); + result.AddRange(FlattenTree(node.ChildNodes, getSortKey, layer + 1)); + } + + return result; + } + public static void SelectSubtreeById(this List nodes, List subtree, TEntityId rootId, Func getId, Func getParentId) where TEntity : class where TEntityId : struct + { + TEntity? node = nodes.FirstOrDefault(n => getId(n).Equals(rootId)); + if (node != null) + { + subtree.Add(node); + List children = nodes.Where(n => getParentId(n).Equals(rootId)).ToList(); + foreach (var child in children) + { + SelectSubtreeById(nodes, subtree, getId(child), getId, getParentId); + } + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Web/Models/BaseViewModel.cs b/LibCommon/Kit.Core.Helpers/Web/Models/BaseViewModel.cs new file mode 100644 index 0000000..c76bc18 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Web/Models/BaseViewModel.cs @@ -0,0 +1,93 @@ +using System.Text.Json.Serialization; + +namespace Microsoft.AspNetCore.Mvc +{ + abstract public class VersionIdModel + { + public int VersionId { get; set; } = 0; + } + + public enum ViewMode + { + Full = 0, + Partial = 1, + IFrameFull = 2, + IFramePartial = 3, + } + + public class BaseViewModel : VersionIdModel + { + public bool NeedScripts { get; set; } = true; + + public ViewMode ViewMode { get; set; } = ViewMode.Full; + + public const string BootstrapVersionDefault = "5.3"; + public const string FaIconsVersionDefault = "6.5"; + + #region Инфа по JQuery + + public bool JQueryRequired { get; set; } = true; + + #endregion + + #region Инфа по JSTree + + public bool JsTreeRequired { get; set; } = true; + + #endregion + + #region Инфа по бутстрапу + + public bool BootstrapRequired { get; set; } = true; + public string BootstrapVersion { get; set; } = BootstrapVersionDefault; + public bool CanLoadBootstrap { get => (BootstrapVersion ?? string.Empty).StartsWith(BootstrapVersionDefault); } + public bool NeedLoadBootstrap { get => BootstrapRequired && CanLoadBootstrap; } + public bool NeedDowngradeBootstrap { get => CanLoadBootstrap == false; } + + #endregion + + #region Инфа по иконкам + + [JsonPropertyName("faiRequired")] + public bool FaIconsRequired { get; set; } = true; + [JsonPropertyName("faiVersion")] + public string FaIconsVersion { get; set; } = FaIconsVersionDefault; + public bool CanLoadFaIcons { get => (FaIconsVersion ?? string.Empty).StartsWith(FaIconsVersionDefault); } + public bool NeedLoadFaIcons { get => FaIconsRequired && CanLoadFaIcons; } + public bool NeedDowngradeFaIcons { get => CanLoadBootstrap == false; } + + #endregion + + public BaseViewModel() { } + public BaseViewModel(BaseViewModel baseViewModel) + { + if (baseViewModel == null) return; + + NeedScripts = baseViewModel.NeedScripts; + ViewMode = baseViewModel.ViewMode; + JQueryRequired = baseViewModel.JQueryRequired; + JsTreeRequired = baseViewModel.JsTreeRequired; + BootstrapRequired = baseViewModel.BootstrapRequired; + BootstrapVersion = baseViewModel.BootstrapVersion; + FaIconsRequired = baseViewModel.FaIconsRequired; + FaIconsVersion = baseViewModel.FaIconsVersion; + } + + public void RefillByViewMode() + { + if (ViewMode == ViewMode.IFrameFull || ViewMode == ViewMode.IFramePartial) + { + NeedScripts = true; + + BootstrapRequired = true; + BootstrapVersion = BootstrapVersionDefault; + + FaIconsRequired = true; + FaIconsVersion = FaIconsVersionDefault; + + JQueryRequired = true; + JsTreeRequired = true; + } + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Web/Models/ClassifierJSONModel.cs b/LibCommon/Kit.Core.Helpers/Web/Models/ClassifierJSONModel.cs new file mode 100644 index 0000000..25daf04 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Web/Models/ClassifierJSONModel.cs @@ -0,0 +1,32 @@ +namespace Kit.Helpers +{ + public class ClassifierJSONModel : JsTreeNodeModel { } + + [Helpers.NeedToGenerateJsDoc] + public class RequirementTreeNodeModel : JsTreeNodeModel { } + + public class JsTreeNodeModel : Helpers.Web.Models.JsTreeNodeModelBase + { + public string description { get; set; } + public int categoryId { get; set; } + } + public class DataClassifier + { + public string key { get; set; } + public string code { get; set; } + public string title { get; set; } + public string sort { get; set; } + public int section { get; set; } + public bool noContextAdd { get; set; } + public bool noContextEdit { get; set; } + public bool noContextDelete { get; set; } + + } + public class RequirementNodeDataModel + { + public string requirementId { get; set; } + public string requirementCode { get; set; } + public string sourceType { get; set; } + } + public class StateClassifier : Helpers.Web.Models.JsTreeNodeStateModel { } +} diff --git a/LibCommon/Kit.Core.Helpers/Web/Models/IframeProxyViewModel.cs b/LibCommon/Kit.Core.Helpers/Web/Models/IframeProxyViewModel.cs new file mode 100644 index 0000000..64187d4 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Web/Models/IframeProxyViewModel.cs @@ -0,0 +1,9 @@ +namespace Microsoft.AspNetCore.Mvc +{ + public class IframeProxyViewModel : BaseViewModel + { + public string UrlIframe { get; set; }= string.Empty; + public IframeProxyViewModel() { } + public IframeProxyViewModel(BaseViewModel model) : base(model) { } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Web/Models/JsTree/JsTreeNodeModelBase.cs b/LibCommon/Kit.Core.Helpers/Web/Models/JsTree/JsTreeNodeModelBase.cs new file mode 100644 index 0000000..8f072db --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Web/Models/JsTree/JsTreeNodeModelBase.cs @@ -0,0 +1,35 @@ +using System.ComponentModel; + +namespace Kit.Helpers.Web.Models +{ + public class JsTreeNodeModelBase + { + /// Id узла. Обязателен для заполнения + [Description("Id узла. Обязателен для заполнения")] + public string id { get; set; } + + /// Id родительского узла. Обязателен для заполнения. Если является корневым, указывать значение '#' + [Description("Id родительского узла. Обязателен для заполнения. Если является корневым, указывать значение '#'")] + public string parent { get; set; } + + /// Текст узла + [Description("Текст узла")] + public string text { get; set; } + + /// Иконка узла + [Description("Иконка узла")] + public string icon { get; set; } + + /// Тип узла + [Description("Тип узла")] + public string type { get; set; } + + /// Дополнительные данные узла + [Description("Дополнительные данные узла")] + public TData data { get; set; } + + /// Состояние узла + [Description("Состояние узла")] + public JsTreeNodeStateModel state { get; set; } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Web/Models/JsTree/JsTreeNodeStateModel.cs b/LibCommon/Kit.Core.Helpers/Web/Models/JsTree/JsTreeNodeStateModel.cs new file mode 100644 index 0000000..fac2d86 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Web/Models/JsTree/JsTreeNodeStateModel.cs @@ -0,0 +1,22 @@ +using System.ComponentModel; + +namespace Kit.Helpers.Web.Models +{ + /// Описание состояния узла JsTree + [NeedToGenerateJsDoc] + [Description("Описание состояния узла JsTree")] + public class JsTreeNodeStateModel + { + /// Открыт ли узел + [Description("Открыт ли узел")] + public bool opened { get; set; } + + /// Выключен ли узел + [Description("Выключен ли узел")] + public bool disabled { get; set; } + + /// Выбран ли узел + [Description("Выбран ли узел")] + public bool selected { get; set; } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Web/Models/JsonResultDefault.cs b/LibCommon/Kit.Core.Helpers/Web/Models/JsonResultDefault.cs new file mode 100644 index 0000000..0556ea1 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Web/Models/JsonResultDefault.cs @@ -0,0 +1,43 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Kit.Helpers +{ + public class JsonResultSuccess + { + public T? SuccessMessage { get; set; } + } + + public class JsonResultError + { + public string? ErrorMessage { get; set; } + public string? ErrorStackTrace { get; set; } + public string? RedirectUrl { get; set; } + } + + public static class JsonResultExt + { + public static JsonResult JsonSuccess(this TController controller) + where TController : Controller + => controller.Json(new JsonResultSuccess { SuccessMessage = null }); + + public static JsonResult JsonSuccess(this TController controller) + where TController : Controller + => controller.Json(new JsonResultSuccess { SuccessMessage = default }); + + public static JsonResult JsonSuccess(this TController controller, TSuccessMessage? successMessage) + where TController : Controller + => controller.Json(new JsonResultSuccess { SuccessMessage = successMessage }); + + public static JsonResult JsonError(this TController controller, string errorMessage) + where TController : Controller + => controller.Json(new JsonResultError { ErrorMessage = errorMessage ?? string.Empty }); + + public static JsonResult JsonError(this TController controller, string errorMessage, string errorStackTrace) + where TController : Controller + => controller.Json(new JsonResultError { ErrorMessage = errorMessage ?? string.Empty, ErrorStackTrace = errorStackTrace ?? string.Empty }); + + public static JsonResult JsonError(this TController controller, string errorMessage, string errorStackTrace, string? redirectUrl) + where TController : Controller + => controller.Json(new JsonResultError { ErrorMessage = errorMessage ?? string.Empty, ErrorStackTrace = errorStackTrace ?? string.Empty, RedirectUrl = redirectUrl }); + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/Web/Models/LayoutModel.cs b/LibCommon/Kit.Core.Helpers/Web/Models/LayoutModel.cs new file mode 100644 index 0000000..c5bebdb --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Web/Models/LayoutModel.cs @@ -0,0 +1,25 @@ +namespace Microsoft.AspNetCore.Mvc +{ + using Microsoft.AspNetCore.Http; + using Kit.Helpers; + + public class LayoutModel + { + //public abstract LayoutMenuTabs LayoutMenuTabActive { get; } + public string CurrentUrl { get; private set; } = string.Empty; + public string ControllerName { get; private set; } = string.Empty; + public string ActionName { get; private set; } = string.Empty; + + public virtual string ActionCreateGet => "CreateGet"; + public virtual string ActionCreatePost => "CreatePost"; + public virtual string ActionEditGet => "EditGet"; + public virtual string ActionEditPost => "EditPost"; + + public void Fill(HttpContext httpContext, ControllerContext controllerContext) + { + CurrentUrl = httpContext.Request.AbsoluteUrl(checkForwardedProto: true); + ControllerName = controllerContext.ActionDescriptor?.ControllerName ?? string.Empty; + ActionName = controllerContext.ActionDescriptor?.ActionName ?? string.Empty; + } + } +} diff --git a/LibCommon/Kit.Core.Helpers/Web/Models/ListViewModel.cs b/LibCommon/Kit.Core.Helpers/Web/Models/ListViewModel.cs new file mode 100644 index 0000000..47dbd60 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/Web/Models/ListViewModel.cs @@ -0,0 +1,7 @@ +namespace Microsoft.AspNetCore.Mvc +{ + public class ListViewModel: BaseViewModel + { + public int ParentId { get; set; } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/bin/Debug/net8.0/Kit.Core.Helpers.deps.json b/LibCommon/Kit.Core.Helpers/bin/Debug/net8.0/Kit.Core.Helpers.deps.json new file mode 100644 index 0000000..bfbdb82 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/bin/Debug/net8.0/Kit.Core.Helpers.deps.json @@ -0,0 +1,293 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v8.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v8.0": { + "Kit.Core.Helpers/1.0.0": { + "dependencies": { + "Microsoft.Data.Sqlite": "9.0.5", + "Npgsql": "1.0.0", + "System.Management": "9.0.5" + }, + "runtime": { + "Kit.Core.Helpers.dll": {} + } + }, + "Microsoft.Data.Sqlite/9.0.5": { + "dependencies": { + "Microsoft.Data.Sqlite.Core": "9.0.5", + "SQLitePCLRaw.bundle_e_sqlite3": "2.1.10", + "SQLitePCLRaw.core": "2.1.10" + } + }, + "Microsoft.Data.Sqlite.Core/9.0.5": { + "dependencies": { + "SQLitePCLRaw.core": "2.1.10" + }, + "runtime": { + "lib/net8.0/Microsoft.Data.Sqlite.dll": { + "assemblyVersion": "9.0.5.0", + "fileVersion": "9.0.525.21604" + } + } + }, + "SQLitePCLRaw.bundle_e_sqlite3/2.1.10": { + "dependencies": { + "SQLitePCLRaw.lib.e_sqlite3": "2.1.10", + "SQLitePCLRaw.provider.e_sqlite3": "2.1.10" + }, + "runtime": { + "lib/netstandard2.0/SQLitePCLRaw.batteries_v2.dll": { + "assemblyVersion": "2.1.10.2445", + "fileVersion": "2.1.10.2445" + } + } + }, + "SQLitePCLRaw.core/2.1.10": { + "dependencies": { + "System.Memory": "4.5.3" + }, + "runtime": { + "lib/netstandard2.0/SQLitePCLRaw.core.dll": { + "assemblyVersion": "2.1.10.2445", + "fileVersion": "2.1.10.2445" + } + } + }, + "SQLitePCLRaw.lib.e_sqlite3/2.1.10": { + "runtimeTargets": { + "runtimes/browser-wasm/nativeassets/net8.0/e_sqlite3.a": { + "rid": "browser-wasm", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-arm/native/libe_sqlite3.so": { + "rid": "linux-arm", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-arm64/native/libe_sqlite3.so": { + "rid": "linux-arm64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-armel/native/libe_sqlite3.so": { + "rid": "linux-armel", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-mips64/native/libe_sqlite3.so": { + "rid": "linux-mips64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-musl-arm/native/libe_sqlite3.so": { + "rid": "linux-musl-arm", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-musl-arm64/native/libe_sqlite3.so": { + "rid": "linux-musl-arm64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-musl-s390x/native/libe_sqlite3.so": { + "rid": "linux-musl-s390x", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-musl-x64/native/libe_sqlite3.so": { + "rid": "linux-musl-x64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-ppc64le/native/libe_sqlite3.so": { + "rid": "linux-ppc64le", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-s390x/native/libe_sqlite3.so": { + "rid": "linux-s390x", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-x64/native/libe_sqlite3.so": { + "rid": "linux-x64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-x86/native/libe_sqlite3.so": { + "rid": "linux-x86", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/maccatalyst-arm64/native/libe_sqlite3.dylib": { + "rid": "maccatalyst-arm64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/maccatalyst-x64/native/libe_sqlite3.dylib": { + "rid": "maccatalyst-x64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/osx-arm64/native/libe_sqlite3.dylib": { + "rid": "osx-arm64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/osx-x64/native/libe_sqlite3.dylib": { + "rid": "osx-x64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/win-arm/native/e_sqlite3.dll": { + "rid": "win-arm", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/win-arm64/native/e_sqlite3.dll": { + "rid": "win-arm64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/win-x64/native/e_sqlite3.dll": { + "rid": "win-x64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/win-x86/native/e_sqlite3.dll": { + "rid": "win-x86", + "assetType": "native", + "fileVersion": "0.0.0.0" + } + } + }, + "SQLitePCLRaw.provider.e_sqlite3/2.1.10": { + "dependencies": { + "SQLitePCLRaw.core": "2.1.10" + }, + "runtime": { + "lib/net6.0/SQLitePCLRaw.provider.e_sqlite3.dll": { + "assemblyVersion": "2.1.10.2445", + "fileVersion": "2.1.10.2445" + } + } + }, + "System.CodeDom/9.0.5": { + "runtime": { + "lib/net8.0/System.CodeDom.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.525.21509" + } + } + }, + "System.Management/9.0.5": { + "dependencies": { + "System.CodeDom": "9.0.5" + }, + "runtime": { + "lib/net8.0/System.Management.dll": { + "assemblyVersion": "9.0.0.5", + "fileVersion": "9.0.525.21509" + } + }, + "runtimeTargets": { + "runtimes/win/lib/net8.0/System.Management.dll": { + "rid": "win", + "assetType": "runtime", + "assemblyVersion": "9.0.0.5", + "fileVersion": "9.0.525.21509" + } + } + }, + "System.Memory/4.5.3": {}, + "Npgsql/1.0.0": { + "runtime": { + "Npgsql.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + } + } + }, + "libraries": { + "Kit.Core.Helpers/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "Microsoft.Data.Sqlite/9.0.5": { + "type": "package", + "serviceable": true, + "sha512": "sha512-Fht/vwX7uzPaIdlj1xtcpKD225GXjPOj7iW8934NNiTfBX5uqeCy2TdlPWPpLj+VPty/UmfgUSJSb55hXHqJMw==", + "path": "microsoft.data.sqlite/9.0.5", + "hashPath": "microsoft.data.sqlite.9.0.5.nupkg.sha512" + }, + "Microsoft.Data.Sqlite.Core/9.0.5": { + "type": "package", + "serviceable": true, + "sha512": "sha512-cP5eBSqra4Ae80X72g0h2N+jdrA4BgoMQmz9JaQmKAEXUHw9N21DPIBqIyMjOo2fK9ISiGytlAOxBAJf1hEvqg==", + "path": "microsoft.data.sqlite.core/9.0.5", + "hashPath": "microsoft.data.sqlite.core.9.0.5.nupkg.sha512" + }, + "SQLitePCLRaw.bundle_e_sqlite3/2.1.10": { + "type": "package", + "serviceable": true, + "sha512": "sha512-UxWuisvZ3uVcVOLJQv7urM/JiQH+v3TmaJc1BLKl5Dxfm/nTzTUrqswCqg/INiYLi61AXnHo1M1JPmPqqLnAdg==", + "path": "sqlitepclraw.bundle_e_sqlite3/2.1.10", + "hashPath": "sqlitepclraw.bundle_e_sqlite3.2.1.10.nupkg.sha512" + }, + "SQLitePCLRaw.core/2.1.10": { + "type": "package", + "serviceable": true, + "sha512": "sha512-Ii8JCbC7oiVclaE/mbDEK000EFIJ+ShRPwAvvV89GOZhQ+ZLtlnSWl6ksCNMKu/VGXA4Nfi2B7LhN/QFN9oBcw==", + "path": "sqlitepclraw.core/2.1.10", + "hashPath": "sqlitepclraw.core.2.1.10.nupkg.sha512" + }, + "SQLitePCLRaw.lib.e_sqlite3/2.1.10": { + "type": "package", + "serviceable": true, + "sha512": "sha512-mAr69tDbnf3QJpRy2nJz8Qdpebdil00fvycyByR58Cn9eARvR+UiG2Vzsp+4q1tV3ikwiYIjlXCQFc12GfebbA==", + "path": "sqlitepclraw.lib.e_sqlite3/2.1.10", + "hashPath": "sqlitepclraw.lib.e_sqlite3.2.1.10.nupkg.sha512" + }, + "SQLitePCLRaw.provider.e_sqlite3/2.1.10": { + "type": "package", + "serviceable": true, + "sha512": "sha512-uZVTi02C1SxqzgT0HqTWatIbWGb40iIkfc3FpFCpE/r7g6K0PqzDUeefL6P6HPhDtc6BacN3yQysfzP7ks+wSQ==", + "path": "sqlitepclraw.provider.e_sqlite3/2.1.10", + "hashPath": "sqlitepclraw.provider.e_sqlite3.2.1.10.nupkg.sha512" + }, + "System.CodeDom/9.0.5": { + "type": "package", + "serviceable": true, + "sha512": "sha512-cuzLM2MWutf9ZBEMPYYfd0DXwYdvntp7VCT6a/wvbKCa2ZuvGmW74xi+YBa2mrfEieAXqM4TNKlMmSnfAfpUoQ==", + "path": "system.codedom/9.0.5", + "hashPath": "system.codedom.9.0.5.nupkg.sha512" + }, + "System.Management/9.0.5": { + "type": "package", + "serviceable": true, + "sha512": "sha512-n6o9PZm9p25+zAzC3/48K0oHnaPKTInRrxqFq1fi/5TPbMLjuoCm/h//mS3cUmSy+9AO1Z+qsC/Ilt/ZFatv5Q==", + "path": "system.management/9.0.5", + "hashPath": "system.management.9.0.5.nupkg.sha512" + }, + "System.Memory/4.5.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA==", + "path": "system.memory/4.5.3", + "hashPath": "system.memory.4.5.3.nupkg.sha512" + }, + "Npgsql/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/bin/Debug/net8.0/Kit.Core.Helpers.dll b/LibCommon/Kit.Core.Helpers/bin/Debug/net8.0/Kit.Core.Helpers.dll new file mode 100644 index 0000000..5105b7c Binary files /dev/null and b/LibCommon/Kit.Core.Helpers/bin/Debug/net8.0/Kit.Core.Helpers.dll differ diff --git a/LibCommon/Kit.Core.Helpers/bin/Debug/net8.0/Kit.Core.Helpers.pdb b/LibCommon/Kit.Core.Helpers/bin/Debug/net8.0/Kit.Core.Helpers.pdb new file mode 100644 index 0000000..b92be39 Binary files /dev/null and b/LibCommon/Kit.Core.Helpers/bin/Debug/net8.0/Kit.Core.Helpers.pdb differ diff --git a/LibCommon/Kit.Core.Helpers/bin/Debug/net8.0/Npgsql.dll b/LibCommon/Kit.Core.Helpers/bin/Debug/net8.0/Npgsql.dll new file mode 100644 index 0000000..dab0131 Binary files /dev/null and b/LibCommon/Kit.Core.Helpers/bin/Debug/net8.0/Npgsql.dll differ diff --git a/LibCommon/Kit.Core.Helpers/bin/Debug/net8.0/Npgsql.pdb b/LibCommon/Kit.Core.Helpers/bin/Debug/net8.0/Npgsql.pdb new file mode 100644 index 0000000..4ab92eb Binary files /dev/null and b/LibCommon/Kit.Core.Helpers/bin/Debug/net8.0/Npgsql.pdb differ diff --git a/Kit.Core.Helpers/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs similarity index 100% rename from Kit.Core.Helpers/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs rename to LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs diff --git a/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.568010E0.Up2Date b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.568010E0.Up2Date new file mode 100644 index 0000000..e69de29 diff --git a/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.AssemblyInfo.cs b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.AssemblyInfo.cs similarity index 94% rename from Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.AssemblyInfo.cs rename to LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.AssemblyInfo.cs index 93487aa..dfb3b6d 100644 --- a/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.AssemblyInfo.cs +++ b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.AssemblyInfo.cs @@ -14,7 +14,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("Kit.Core.Helpers")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+37e709e30d2205dd559831f65211b8dd5d40a303")] [assembly: System.Reflection.AssemblyProductAttribute("Kit.Core.Helpers")] [assembly: System.Reflection.AssemblyTitleAttribute("Kit.Core.Helpers")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.AssemblyInfoInputs.cache b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.AssemblyInfoInputs.cache new file mode 100644 index 0000000..9a1c8f3 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +97444a81c4f111c6498797caadeae7a8d9fe61312a29ee532ed1b5a714f7b4bf diff --git a/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.GeneratedMSBuildEditorConfig.editorconfig b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.GeneratedMSBuildEditorConfig.editorconfig similarity index 89% rename from Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.GeneratedMSBuildEditorConfig.editorconfig rename to LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.GeneratedMSBuildEditorConfig.editorconfig index 0051162..0a10793 100644 --- a/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.GeneratedMSBuildEditorConfig.editorconfig +++ b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.GeneratedMSBuildEditorConfig.editorconfig @@ -8,7 +8,7 @@ build_property.PlatformNeutralAssembly = build_property.EnforceExtendedAnalyzerRules = build_property._SupportedPlatformList = Linux,macOS,Windows build_property.RootNamespace = Kit.Core.Helpers -build_property.ProjectDir = C:\KIT\Kit.Core\Kit.Core.Helpers\ +build_property.ProjectDir = C:\KIT\Kit.Core\LibCommon\Kit.Core.Helpers\ build_property.EnableComHosting = build_property.EnableGeneratedComInterfaceComImportInterop = build_property.EffectiveAnalysisLevelStyle = 8.0 diff --git a/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.GlobalUsings.g.cs b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.GlobalUsings.g.cs similarity index 100% rename from Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.GlobalUsings.g.cs rename to LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.GlobalUsings.g.cs diff --git a/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.assets.cache b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.assets.cache new file mode 100644 index 0000000..7b07ae2 Binary files /dev/null and b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.assets.cache differ diff --git a/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.csproj.AssemblyReference.cache b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.csproj.AssemblyReference.cache new file mode 100644 index 0000000..048fc00 Binary files /dev/null and b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.csproj.AssemblyReference.cache differ diff --git a/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.csproj.CoreCompileInputs.cache b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..195f041 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +c96e443025dd7fbe131e0862ac9326cc12f0c56fdda80f6d16c391ffd63fba0f diff --git a/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.csproj.FileListAbsolute.txt b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..b9a877b --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.csproj.FileListAbsolute.txt @@ -0,0 +1,15 @@ +C:\KIT\Kit.Core\LibCommon\Kit.Core.Helpers\obj\Debug\net8.0\Kit.Core.Helpers.GeneratedMSBuildEditorConfig.editorconfig +C:\KIT\Kit.Core\LibCommon\Kit.Core.Helpers\obj\Debug\net8.0\Kit.Core.Helpers.AssemblyInfoInputs.cache +C:\KIT\Kit.Core\LibCommon\Kit.Core.Helpers\obj\Debug\net8.0\Kit.Core.Helpers.AssemblyInfo.cs +C:\KIT\Kit.Core\LibCommon\Kit.Core.Helpers\obj\Debug\net8.0\Kit.Core.Helpers.csproj.CoreCompileInputs.cache +C:\KIT\Kit.Core\LibCommon\Kit.Core.Helpers\obj\Debug\net8.0\Kit.Core.Helpers.csproj.AssemblyReference.cache +C:\KIT\Kit.Core\LibCommon\Kit.Core.Helpers\bin\Debug\net8.0\Kit.Core.Helpers.deps.json +C:\KIT\Kit.Core\LibCommon\Kit.Core.Helpers\bin\Debug\net8.0\Kit.Core.Helpers.dll +C:\KIT\Kit.Core\LibCommon\Kit.Core.Helpers\bin\Debug\net8.0\Kit.Core.Helpers.pdb +C:\KIT\Kit.Core\LibCommon\Kit.Core.Helpers\bin\Debug\net8.0\Npgsql.dll +C:\KIT\Kit.Core\LibCommon\Kit.Core.Helpers\bin\Debug\net8.0\Npgsql.pdb +C:\KIT\Kit.Core\LibCommon\Kit.Core.Helpers\obj\Debug\net8.0\Kit.Core.568010E0.Up2Date +C:\KIT\Kit.Core\LibCommon\Kit.Core.Helpers\obj\Debug\net8.0\Kit.Core.Helpers.dll +C:\KIT\Kit.Core\LibCommon\Kit.Core.Helpers\obj\Debug\net8.0\refint\Kit.Core.Helpers.dll +C:\KIT\Kit.Core\LibCommon\Kit.Core.Helpers\obj\Debug\net8.0\Kit.Core.Helpers.pdb +C:\KIT\Kit.Core\LibCommon\Kit.Core.Helpers\obj\Debug\net8.0\ref\Kit.Core.Helpers.dll diff --git a/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.dll b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.dll new file mode 100644 index 0000000..5105b7c Binary files /dev/null and b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.dll differ diff --git a/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.pdb b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.pdb new file mode 100644 index 0000000..b92be39 Binary files /dev/null and b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.pdb differ diff --git a/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/ref/Kit.Core.Helpers.dll b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/ref/Kit.Core.Helpers.dll new file mode 100644 index 0000000..c595329 Binary files /dev/null and b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/ref/Kit.Core.Helpers.dll differ diff --git a/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/refint/Kit.Core.Helpers.dll b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/refint/Kit.Core.Helpers.dll new file mode 100644 index 0000000..c595329 Binary files /dev/null and b/LibCommon/Kit.Core.Helpers/obj/Debug/net8.0/refint/Kit.Core.Helpers.dll differ diff --git a/LibCommon/Kit.Core.Helpers/obj/Kit.Core.Helpers.csproj.nuget.dgspec.json b/LibCommon/Kit.Core.Helpers/obj/Kit.Core.Helpers.csproj.nuget.dgspec.json new file mode 100644 index 0000000..0f1cbf9 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/obj/Kit.Core.Helpers.csproj.nuget.dgspec.json @@ -0,0 +1,157 @@ +{ + "format": 1, + "restore": { + "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj": {} + }, + "projects": { + "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj", + "projectName": "Kit.Core.Helpers", + "projectPath": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj", + "packagesPath": "C:\\Users\\user\\.nuget\\packages\\", + "outputPath": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Core.Helpers\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\user\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "C:\\GIT\\RiskProf.Modules.Core\\RiskProf.LK.Back\\RiskProf.LK.Back\\packages": {}, + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": { + "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\Npgsql.csproj": { + "projectPath": "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\Npgsql.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "dependencies": { + "Microsoft.Data.Sqlite": { + "target": "Package", + "version": "[9.0.5, )" + }, + "System.Management": { + "target": "Package", + "version": "[9.0.5, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.302/PortableRuntimeIdentifierGraph.json" + } + } + }, + "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\Npgsql.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\Npgsql.csproj", + "projectName": "Npgsql", + "projectPath": "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\Npgsql.csproj", + "packagesPath": "C:\\Users\\user\\.nuget\\packages\\", + "outputPath": "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\user\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "C:\\GIT\\RiskProf.Modules.Core\\RiskProf.LK.Back\\RiskProf.LK.Back\\packages": {}, + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.302/PortableRuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/Kit.Core.Helpers/obj/Kit.Core.Helpers.csproj.nuget.g.props b/LibCommon/Kit.Core.Helpers/obj/Kit.Core.Helpers.csproj.nuget.g.props similarity index 100% rename from Kit.Core.Helpers/obj/Kit.Core.Helpers.csproj.nuget.g.props rename to LibCommon/Kit.Core.Helpers/obj/Kit.Core.Helpers.csproj.nuget.g.props diff --git a/LibCommon/Kit.Core.Helpers/obj/Kit.Core.Helpers.csproj.nuget.g.targets b/LibCommon/Kit.Core.Helpers/obj/Kit.Core.Helpers.csproj.nuget.g.targets new file mode 100644 index 0000000..ce16130 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/obj/Kit.Core.Helpers.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/obj/project.assets.json b/LibCommon/Kit.Core.Helpers/obj/project.assets.json new file mode 100644 index 0000000..513bb39 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/obj/project.assets.json @@ -0,0 +1,536 @@ +{ + "version": 3, + "targets": { + "net8.0": { + "Microsoft.Data.Sqlite/9.0.5": { + "type": "package", + "dependencies": { + "Microsoft.Data.Sqlite.Core": "9.0.5", + "SQLitePCLRaw.bundle_e_sqlite3": "2.1.10", + "SQLitePCLRaw.core": "2.1.10" + }, + "compile": { + "lib/netstandard2.0/_._": {} + }, + "runtime": { + "lib/netstandard2.0/_._": {} + } + }, + "Microsoft.Data.Sqlite.Core/9.0.5": { + "type": "package", + "dependencies": { + "SQLitePCLRaw.core": "2.1.10" + }, + "compile": { + "lib/net8.0/Microsoft.Data.Sqlite.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Microsoft.Data.Sqlite.dll": { + "related": ".xml" + } + } + }, + "SQLitePCLRaw.bundle_e_sqlite3/2.1.10": { + "type": "package", + "dependencies": { + "SQLitePCLRaw.lib.e_sqlite3": "2.1.10", + "SQLitePCLRaw.provider.e_sqlite3": "2.1.10" + }, + "compile": { + "lib/netstandard2.0/SQLitePCLRaw.batteries_v2.dll": {} + }, + "runtime": { + "lib/netstandard2.0/SQLitePCLRaw.batteries_v2.dll": {} + } + }, + "SQLitePCLRaw.core/2.1.10": { + "type": "package", + "dependencies": { + "System.Memory": "4.5.3" + }, + "compile": { + "lib/netstandard2.0/SQLitePCLRaw.core.dll": {} + }, + "runtime": { + "lib/netstandard2.0/SQLitePCLRaw.core.dll": {} + } + }, + "SQLitePCLRaw.lib.e_sqlite3/2.1.10": { + "type": "package", + "compile": { + "lib/netstandard2.0/_._": {} + }, + "runtime": { + "lib/netstandard2.0/_._": {} + }, + "build": { + "buildTransitive/net8.0/SQLitePCLRaw.lib.e_sqlite3.targets": {} + }, + "runtimeTargets": { + "runtimes/browser-wasm/nativeassets/net8.0/e_sqlite3.a": { + "assetType": "native", + "rid": "browser-wasm" + }, + "runtimes/linux-arm/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-arm" + }, + "runtimes/linux-arm64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-arm64" + }, + "runtimes/linux-armel/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-armel" + }, + "runtimes/linux-mips64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-mips64" + }, + "runtimes/linux-musl-arm/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-musl-arm" + }, + "runtimes/linux-musl-arm64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-musl-arm64" + }, + "runtimes/linux-musl-s390x/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-musl-s390x" + }, + "runtimes/linux-musl-x64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-musl-x64" + }, + "runtimes/linux-ppc64le/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-ppc64le" + }, + "runtimes/linux-s390x/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-s390x" + }, + "runtimes/linux-x64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-x64" + }, + "runtimes/linux-x86/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-x86" + }, + "runtimes/maccatalyst-arm64/native/libe_sqlite3.dylib": { + "assetType": "native", + "rid": "maccatalyst-arm64" + }, + "runtimes/maccatalyst-x64/native/libe_sqlite3.dylib": { + "assetType": "native", + "rid": "maccatalyst-x64" + }, + "runtimes/osx-arm64/native/libe_sqlite3.dylib": { + "assetType": "native", + "rid": "osx-arm64" + }, + "runtimes/osx-x64/native/libe_sqlite3.dylib": { + "assetType": "native", + "rid": "osx-x64" + }, + "runtimes/win-arm/native/e_sqlite3.dll": { + "assetType": "native", + "rid": "win-arm" + }, + "runtimes/win-arm64/native/e_sqlite3.dll": { + "assetType": "native", + "rid": "win-arm64" + }, + "runtimes/win-x64/native/e_sqlite3.dll": { + "assetType": "native", + "rid": "win-x64" + }, + "runtimes/win-x86/native/e_sqlite3.dll": { + "assetType": "native", + "rid": "win-x86" + } + } + }, + "SQLitePCLRaw.provider.e_sqlite3/2.1.10": { + "type": "package", + "dependencies": { + "SQLitePCLRaw.core": "2.1.10" + }, + "compile": { + "lib/net6.0/SQLitePCLRaw.provider.e_sqlite3.dll": {} + }, + "runtime": { + "lib/net6.0/SQLitePCLRaw.provider.e_sqlite3.dll": {} + } + }, + "System.CodeDom/9.0.5": { + "type": "package", + "compile": { + "lib/net8.0/System.CodeDom.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/System.CodeDom.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "System.Management/9.0.5": { + "type": "package", + "dependencies": { + "System.CodeDom": "9.0.5" + }, + "compile": { + "lib/net8.0/System.Management.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/System.Management.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + }, + "runtimeTargets": { + "runtimes/win/lib/net8.0/System.Management.dll": { + "assetType": "runtime", + "rid": "win" + } + } + }, + "System.Memory/4.5.3": { + "type": "package", + "compile": { + "ref/netcoreapp2.1/_._": {} + }, + "runtime": { + "lib/netcoreapp2.1/_._": {} + } + }, + "Npgsql/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v8.0", + "compile": { + "bin/placeholder/Npgsql.dll": {} + }, + "runtime": { + "bin/placeholder/Npgsql.dll": {} + } + } + } + }, + "libraries": { + "Microsoft.Data.Sqlite/9.0.5": { + "sha512": "Fht/vwX7uzPaIdlj1xtcpKD225GXjPOj7iW8934NNiTfBX5uqeCy2TdlPWPpLj+VPty/UmfgUSJSb55hXHqJMw==", + "type": "package", + "path": "microsoft.data.sqlite/9.0.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/netstandard2.0/_._", + "microsoft.data.sqlite.9.0.5.nupkg.sha512", + "microsoft.data.sqlite.nuspec" + ] + }, + "Microsoft.Data.Sqlite.Core/9.0.5": { + "sha512": "cP5eBSqra4Ae80X72g0h2N+jdrA4BgoMQmz9JaQmKAEXUHw9N21DPIBqIyMjOo2fK9ISiGytlAOxBAJf1hEvqg==", + "type": "package", + "path": "microsoft.data.sqlite.core/9.0.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net6.0/Microsoft.Data.Sqlite.dll", + "lib/net6.0/Microsoft.Data.Sqlite.xml", + "lib/net8.0/Microsoft.Data.Sqlite.dll", + "lib/net8.0/Microsoft.Data.Sqlite.xml", + "lib/netstandard2.0/Microsoft.Data.Sqlite.dll", + "lib/netstandard2.0/Microsoft.Data.Sqlite.xml", + "microsoft.data.sqlite.core.9.0.5.nupkg.sha512", + "microsoft.data.sqlite.core.nuspec" + ] + }, + "SQLitePCLRaw.bundle_e_sqlite3/2.1.10": { + "sha512": "UxWuisvZ3uVcVOLJQv7urM/JiQH+v3TmaJc1BLKl5Dxfm/nTzTUrqswCqg/INiYLi61AXnHo1M1JPmPqqLnAdg==", + "type": "package", + "path": "sqlitepclraw.bundle_e_sqlite3/2.1.10", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/monoandroid90/SQLitePCLRaw.batteries_v2.dll", + "lib/net461/SQLitePCLRaw.batteries_v2.dll", + "lib/net6.0-android31.0/SQLitePCLRaw.batteries_v2.dll", + "lib/net6.0-android31.0/SQLitePCLRaw.batteries_v2.xml", + "lib/net6.0-ios14.0/SQLitePCLRaw.batteries_v2.dll", + "lib/net6.0-ios14.2/SQLitePCLRaw.batteries_v2.dll", + "lib/net6.0-tvos10.0/SQLitePCLRaw.batteries_v2.dll", + "lib/netstandard2.0/SQLitePCLRaw.batteries_v2.dll", + "lib/xamarinios10/SQLitePCLRaw.batteries_v2.dll", + "sqlitepclraw.bundle_e_sqlite3.2.1.10.nupkg.sha512", + "sqlitepclraw.bundle_e_sqlite3.nuspec" + ] + }, + "SQLitePCLRaw.core/2.1.10": { + "sha512": "Ii8JCbC7oiVclaE/mbDEK000EFIJ+ShRPwAvvV89GOZhQ+ZLtlnSWl6ksCNMKu/VGXA4Nfi2B7LhN/QFN9oBcw==", + "type": "package", + "path": "sqlitepclraw.core/2.1.10", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/SQLitePCLRaw.core.dll", + "sqlitepclraw.core.2.1.10.nupkg.sha512", + "sqlitepclraw.core.nuspec" + ] + }, + "SQLitePCLRaw.lib.e_sqlite3/2.1.10": { + "sha512": "mAr69tDbnf3QJpRy2nJz8Qdpebdil00fvycyByR58Cn9eARvR+UiG2Vzsp+4q1tV3ikwiYIjlXCQFc12GfebbA==", + "type": "package", + "path": "sqlitepclraw.lib.e_sqlite3/2.1.10", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "buildTransitive/net461/SQLitePCLRaw.lib.e_sqlite3.targets", + "buildTransitive/net6.0/SQLitePCLRaw.lib.e_sqlite3.targets", + "buildTransitive/net7.0/SQLitePCLRaw.lib.e_sqlite3.targets", + "buildTransitive/net8.0/SQLitePCLRaw.lib.e_sqlite3.targets", + "buildTransitive/net9.0/SQLitePCLRaw.lib.e_sqlite3.targets", + "lib/net461/_._", + "lib/netstandard2.0/_._", + "runtimes/browser-wasm/nativeassets/net6.0/e_sqlite3.a", + "runtimes/browser-wasm/nativeassets/net7.0/e_sqlite3.a", + "runtimes/browser-wasm/nativeassets/net8.0/e_sqlite3.a", + "runtimes/browser-wasm/nativeassets/net9.0/e_sqlite3.a", + "runtimes/linux-arm/native/libe_sqlite3.so", + "runtimes/linux-arm64/native/libe_sqlite3.so", + "runtimes/linux-armel/native/libe_sqlite3.so", + "runtimes/linux-mips64/native/libe_sqlite3.so", + "runtimes/linux-musl-arm/native/libe_sqlite3.so", + "runtimes/linux-musl-arm64/native/libe_sqlite3.so", + "runtimes/linux-musl-s390x/native/libe_sqlite3.so", + "runtimes/linux-musl-x64/native/libe_sqlite3.so", + "runtimes/linux-ppc64le/native/libe_sqlite3.so", + "runtimes/linux-s390x/native/libe_sqlite3.so", + "runtimes/linux-x64/native/libe_sqlite3.so", + "runtimes/linux-x86/native/libe_sqlite3.so", + "runtimes/maccatalyst-arm64/native/libe_sqlite3.dylib", + "runtimes/maccatalyst-x64/native/libe_sqlite3.dylib", + "runtimes/osx-arm64/native/libe_sqlite3.dylib", + "runtimes/osx-x64/native/libe_sqlite3.dylib", + "runtimes/win-arm/native/e_sqlite3.dll", + "runtimes/win-arm64/native/e_sqlite3.dll", + "runtimes/win-x64/native/e_sqlite3.dll", + "runtimes/win-x86/native/e_sqlite3.dll", + "runtimes/win10-arm/nativeassets/uap10.0/e_sqlite3.dll", + "runtimes/win10-arm64/nativeassets/uap10.0/e_sqlite3.dll", + "runtimes/win10-x64/nativeassets/uap10.0/e_sqlite3.dll", + "runtimes/win10-x86/nativeassets/uap10.0/e_sqlite3.dll", + "sqlitepclraw.lib.e_sqlite3.2.1.10.nupkg.sha512", + "sqlitepclraw.lib.e_sqlite3.nuspec" + ] + }, + "SQLitePCLRaw.provider.e_sqlite3/2.1.10": { + "sha512": "uZVTi02C1SxqzgT0HqTWatIbWGb40iIkfc3FpFCpE/r7g6K0PqzDUeefL6P6HPhDtc6BacN3yQysfzP7ks+wSQ==", + "type": "package", + "path": "sqlitepclraw.provider.e_sqlite3/2.1.10", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net6.0-windows7.0/SQLitePCLRaw.provider.e_sqlite3.dll", + "lib/net6.0/SQLitePCLRaw.provider.e_sqlite3.dll", + "lib/netstandard2.0/SQLitePCLRaw.provider.e_sqlite3.dll", + "sqlitepclraw.provider.e_sqlite3.2.1.10.nupkg.sha512", + "sqlitepclraw.provider.e_sqlite3.nuspec" + ] + }, + "System.CodeDom/9.0.5": { + "sha512": "cuzLM2MWutf9ZBEMPYYfd0DXwYdvntp7VCT6a/wvbKCa2ZuvGmW74xi+YBa2mrfEieAXqM4TNKlMmSnfAfpUoQ==", + "type": "package", + "path": "system.codedom/9.0.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.CodeDom.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/System.CodeDom.targets", + "lib/net462/System.CodeDom.dll", + "lib/net462/System.CodeDom.xml", + "lib/net8.0/System.CodeDom.dll", + "lib/net8.0/System.CodeDom.xml", + "lib/net9.0/System.CodeDom.dll", + "lib/net9.0/System.CodeDom.xml", + "lib/netstandard2.0/System.CodeDom.dll", + "lib/netstandard2.0/System.CodeDom.xml", + "system.codedom.9.0.5.nupkg.sha512", + "system.codedom.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Management/9.0.5": { + "sha512": "n6o9PZm9p25+zAzC3/48K0oHnaPKTInRrxqFq1fi/5TPbMLjuoCm/h//mS3cUmSy+9AO1Z+qsC/Ilt/ZFatv5Q==", + "type": "package", + "path": "system.management/9.0.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/System.Management.targets", + "lib/net462/_._", + "lib/net8.0/System.Management.dll", + "lib/net8.0/System.Management.xml", + "lib/net9.0/System.Management.dll", + "lib/net9.0/System.Management.xml", + "lib/netstandard2.0/System.Management.dll", + "lib/netstandard2.0/System.Management.xml", + "runtimes/win/lib/net8.0/System.Management.dll", + "runtimes/win/lib/net8.0/System.Management.xml", + "runtimes/win/lib/net9.0/System.Management.dll", + "runtimes/win/lib/net9.0/System.Management.xml", + "system.management.9.0.5.nupkg.sha512", + "system.management.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Memory/4.5.3": { + "sha512": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA==", + "type": "package", + "path": "system.memory/4.5.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/netcoreapp2.1/_._", + "lib/netstandard1.1/System.Memory.dll", + "lib/netstandard1.1/System.Memory.xml", + "lib/netstandard2.0/System.Memory.dll", + "lib/netstandard2.0/System.Memory.xml", + "ref/netcoreapp2.1/_._", + "system.memory.4.5.3.nupkg.sha512", + "system.memory.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "Npgsql/1.0.0": { + "type": "project", + "path": "../../LibExternal/Npgsql/Npgsql.csproj", + "msbuildProject": "../../LibExternal/Npgsql/Npgsql.csproj" + } + }, + "projectFileDependencyGroups": { + "net8.0": [ + "Microsoft.Data.Sqlite >= 9.0.5", + "Npgsql >= 1.0.0", + "System.Management >= 9.0.5" + ] + }, + "packageFolders": { + "C:\\Users\\user\\.nuget\\packages\\": {}, + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj", + "projectName": "Kit.Core.Helpers", + "projectPath": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj", + "packagesPath": "C:\\Users\\user\\.nuget\\packages\\", + "outputPath": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Core.Helpers\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\user\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "C:\\GIT\\RiskProf.Modules.Core\\RiskProf.LK.Back\\RiskProf.LK.Back\\packages": {}, + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": { + "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\Npgsql.csproj": { + "projectPath": "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\Npgsql.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "dependencies": { + "Microsoft.Data.Sqlite": { + "target": "Package", + "version": "[9.0.5, )" + }, + "System.Management": { + "target": "Package", + "version": "[9.0.5, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.302/PortableRuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Core.Helpers/obj/project.nuget.cache b/LibCommon/Kit.Core.Helpers/obj/project.nuget.cache new file mode 100644 index 0000000..9ce9387 --- /dev/null +++ b/LibCommon/Kit.Core.Helpers/obj/project.nuget.cache @@ -0,0 +1,18 @@ +{ + "version": 2, + "dgSpecHash": "onDxFwZQpxQ=", + "success": true, + "projectFilePath": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj", + "expectedPackageFiles": [ + "C:\\Users\\user\\.nuget\\packages\\microsoft.data.sqlite\\9.0.5\\microsoft.data.sqlite.9.0.5.nupkg.sha512", + "C:\\Users\\user\\.nuget\\packages\\microsoft.data.sqlite.core\\9.0.5\\microsoft.data.sqlite.core.9.0.5.nupkg.sha512", + "C:\\Users\\user\\.nuget\\packages\\sqlitepclraw.bundle_e_sqlite3\\2.1.10\\sqlitepclraw.bundle_e_sqlite3.2.1.10.nupkg.sha512", + "C:\\Users\\user\\.nuget\\packages\\sqlitepclraw.core\\2.1.10\\sqlitepclraw.core.2.1.10.nupkg.sha512", + "C:\\Users\\user\\.nuget\\packages\\sqlitepclraw.lib.e_sqlite3\\2.1.10\\sqlitepclraw.lib.e_sqlite3.2.1.10.nupkg.sha512", + "C:\\Users\\user\\.nuget\\packages\\sqlitepclraw.provider.e_sqlite3\\2.1.10\\sqlitepclraw.provider.e_sqlite3.2.1.10.nupkg.sha512", + "C:\\Users\\user\\.nuget\\packages\\system.codedom\\9.0.5\\system.codedom.9.0.5.nupkg.sha512", + "C:\\Users\\user\\.nuget\\packages\\system.management\\9.0.5\\system.management.9.0.5.nupkg.sha512", + "C:\\Users\\user\\.nuget\\packages\\system.memory\\4.5.3\\system.memory.4.5.3.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/LibCommon/Kit.Helpers.OpenXml/Kit.Helpers.OpenXml.csproj b/LibCommon/Kit.Helpers.OpenXml/Kit.Helpers.OpenXml.csproj new file mode 100644 index 0000000..7aa11c9 --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/Kit.Helpers.OpenXml.csproj @@ -0,0 +1,20 @@ + + + ..\..\Build\$(Configuration)\$(TargetFramework) + net8.0 + enable + enable + + + + + + + + + + + + + + diff --git a/LibCommon/Kit.Helpers.OpenXml/OpenXml/Excel/ExcelData.cs b/LibCommon/Kit.Helpers.OpenXml/OpenXml/Excel/ExcelData.cs new file mode 100644 index 0000000..ceddee7 --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/OpenXml/Excel/ExcelData.cs @@ -0,0 +1,102 @@ +using DocumentFormat.OpenXml.Spreadsheet; +namespace Kit.Helpers +{ + public class ExcelStatus + { + public string Message { get; set; } + public bool Success + { + get { return string.IsNullOrWhiteSpace(Message); } + } + } + public class ExcelComment + { + public string Reference { get; set; } + public string Comment { get; set; } + + } + + public class ExcelStyle + { + public string Color { get; set; } + + } + + public class ExcelCell : Object + { + public bool IsHeader { get; set; } + public string Header { get; set; } + public string Value { get; set; } + public PatternFill PatternFill { get; set; } + public ExcelComment Comment { get; set; } + public override string ToString() + { + return this.Value; + } + } + + public class ExcelRow + { + public ExcelRow() + { + Cells = new List(); + } + public List Cells { get; set; } + + public ExcelCell this[int index] + { + get + { + return Cells[index]; + } + set + { + Cells[index] = value; + } + } + + + public ExcelCell this[string header] + { + get + { + var cell = GetByHeader(header); + Check.IsNotNull(cell, "Не найден столбец с именем: {0}".ApplyFormat(header)); + return cell; + } + set + { + var cell = GetByHeader(header); + Check.IsNotNull(cell, "Не найден столбец с именем: {0}".ApplyFormat(header)); + cell = value; + } + } + + public ExcelCell GetByHeader(string header) + { + return Cells.FirstOrDefault(x => x.Header.ToLowerEquals(header)); + } + } + + public class ExcelData + { + public ExcelStatus Status { get; set; } + public Columns ColumnConfigurations { get; set; } + public List Headers { get; set; } + public List> DataRows { get; set; } + public List DataRowCells { get; set; } + public List> PatternFillRows { get; set; } + + public List Comments { get; set; } + public string SheetName { get; set; } + + public ExcelData() + { + Status = new ExcelStatus(); + Headers = new List(); + DataRows = new List>(); + DataRowCells = new List(); + PatternFillRows = new List>(); + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Helpers.OpenXml/OpenXml/Excel/ExcelReader.cs b/LibCommon/Kit.Helpers.OpenXml/OpenXml/Excel/ExcelReader.cs new file mode 100644 index 0000000..8fcf249 --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/OpenXml/Excel/ExcelReader.cs @@ -0,0 +1,451 @@ +namespace Kit.Helpers +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text.RegularExpressions; + using System.IO; + using DocumentFormat.OpenXml.Spreadsheet; + using DocumentFormat.OpenXml.Packaging; + using Microsoft.AspNetCore.Http; + + public class ExcelReader + { + private string GetColumnName(string cellReference) + { + var regex = new Regex("[A-Za-z]+"); + var match = regex.Match(cellReference); + + return match.Value; + } + + private int ConvertColumnNameToNumber(string columnName) + { + var alpha = new Regex("^[A-Z]+$"); + if (!alpha.IsMatch(columnName)) throw new ArgumentException(); + + char[] colLetters = columnName.ToCharArray(); + Array.Reverse(colLetters); + + var convertedValue = 0; + for (int i = 0; i < colLetters.Length; i++) + { + char letter = colLetters[i]; + int current = i == 0 ? letter - 65 : letter - 64; // ASCII 'A' = 65 + convertedValue += current * (int)Math.Pow(26, i); + } + + return convertedValue; + } + + private IEnumerator GetExcelCellEnumerator(Row row) + { + int currentCount = 0; + foreach (Cell cell in row.Descendants()) + { + string columnName = GetColumnName(cell.CellReference); + + int currentColumnIndex = ConvertColumnNameToNumber(columnName); + + for (; currentCount < currentColumnIndex; currentCount++) + { + var emptycell = new Cell() { DataType = null, CellValue = new CellValue(string.Empty) }; + yield return emptycell; + } + + yield return cell; + currentCount++; + } + } + + private string ReadExcelCell(Cell cell, WorkbookPart workbookPart) + { + var cellValue = cell.CellValue; + var text = (cellValue == null) ? cell.InnerText : cellValue.Text; + if ((cell.DataType != null) && (cell.DataType == CellValues.SharedString)) + { + text = workbookPart.SharedStringTablePart.SharedStringTable.Elements().ElementAt( + Convert.ToInt32(cell.CellValue.Text)).InnerText; + } + + return (text ?? string.Empty).Trim(); + } + + private PatternFill GetCellPatternFill(Cell cell, WorkbookPart workbookPart) + { + WorkbookStylesPart styles = workbookPart.WorkbookStylesPart; + if (styles == null) + { + return null; + } + + int cellStyleIndex; + if (cell.StyleIndex == null) // I think (from testing) if the StyleIndex is null + { // then this means use cell style index 0. + cellStyleIndex = 0; // However I did not found it in the open xml + } // specification. + else + { + cellStyleIndex = (int)cell.StyleIndex.Value; + } + + CellFormat cellFormat = (CellFormat)styles.Stylesheet.CellFormats.ChildElements[cellStyleIndex]; + + Fill fill = (Fill)styles.Stylesheet.Fills.ChildElements[(int)cellFormat.FillId.Value]; + return fill.PatternFill; + } + + protected ExcelData ReadSheet(WorkbookPart workbookPart, Sheet sheet) + { + var data = new ExcelData(); + List rows; + try + { + data.SheetName = sheet.Name; + + var worksheetPart = (WorksheetPart)workbookPart.GetPartById(sheet.Id); + var workSheet = worksheetPart.Worksheet; + var columns = workSheet.Descendants().FirstOrDefault(); + data.ColumnConfigurations = columns; + + var comments = GetComments(worksheetPart); + var sheetData = workSheet.Elements().First(); + + this.DeleteBlankRows(sheetData); + + rows = sheetData.Elements().ToList(); + + //if (rows.Count > 0) + //{ + // var row = rows[0]; + // var cellEnumerator = GetExcelCellEnumerator(row); + // while (cellEnumerator.MoveNext()) + // { + // var cell = cellEnumerator.Current; + // var text = ReadExcelCell(cell, workbookPart).Trim(); + // data.Headers.Add(text); + // } + //} + + // Read the sheet data + if (rows.Count > 0) + { + for (var i = 0; i < rows.Count; i++) + { + var cellRow = new ExcelRow(); + var dataRow = new List(); + var patternFillRow = new List(); + var row = rows[i]; + var cellEnumerator = GetExcelCellEnumerator(row); + int z = 0; + while (cellEnumerator.MoveNext()) + { + var cell = cellEnumerator.Current; + var text = ReadExcelCell(cell, workbookPart).Trim(); + var comment = comments.FirstOrDefault(x => x.Reference == cell.CellReference); + var patternFill = GetCellPatternFill(cell, workbookPart); + if (patternFill != null) + { + patternFillRow.Add(patternFill); + } + + dataRow.Add(text); + cellRow.Cells.Add(new ExcelCell + { + IsHeader = i == 0, + Comment = comment, + Header = (i == 0 ? text : z < data.Headers.Count() ? data.Headers[z] : string.Empty), + PatternFill = patternFill, + Value = text + }); + z++; + } + data.DataRowCells.Add(cellRow); + if (i == 0) + { + data.Headers = dataRow; + } + else + { + data.DataRows.Add(dataRow); + data.PatternFillRows.Add(patternFillRow); + } + } + } + + // Read the sheet PatternFill + //if (rows.Count > 1) + //{ + // for (var i = 1; i < rows.Count; i++) + // { + // var dataRow = new List(); + // data.PatternFillRows.Add(dataRow); + // var row = rows[i]; + // var cellEnumerator = GetExcelCellEnumerator(row); + // while (cellEnumerator.MoveNext()) + // { + // var cell = cellEnumerator.Current; + // var patternFill = GetCellPatternFill(cell, workbookPart); + // dataRow.Add(patternFill); + // } + // } + //} + + return data; + } + catch (Exception e) + { + data.Status.Message = "Unable to open the file"; + return data; + } + } + + + protected IEnumerable ReadAllExcel(Stream stream) + { + var datas = new List(); + WorkbookPart workbookPart; List rows; + try + { + var document = SpreadsheetDocument.Open(stream, false); + workbookPart = document.WorkbookPart; + + var sheets = workbookPart.Workbook.Descendants(); + + foreach (var sheet in sheets) + { + var data = ReadSheet(workbookPart, sheet); + datas.Add(data); + } + } + catch (Exception e) + { + datas.First().Status.Message = "Unable to open the file"; + return datas; + } + + return datas; + + } + public IEnumerable ReadAllExcel(IFormFile file) + { + var datas = new List(); + // Check if the file is excel + if (file.Length <= 0) + { + datas.First().Status.Message = "You uploaded an empty file"; + return datas; + } + + if (file.ContentType != "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + { + datas.First().Status.Message = "Please upload a valid excel file of version 2007 and above"; + return datas; + } + + using (Stream stream = file.OpenReadStream()) + { + return ReadAllExcel(stream); + } + // Open the excel document + //WorkbookPart workbookPart; List rows; + //try + //{ + // var document = SpreadsheetDocument.Open(file.InputStream, false); + // workbookPart = document.WorkbookPart; + + // var sheets = workbookPart.Workbook.Descendants(); + + // foreach (var sheet in sheets) + // { + // var data = new ExcelData(); + // data.SheetName = sheet.Name; + + // var worksheetPart = (WorksheetPart)workbookPart.GetPartById(sheet.Id); + // var workSheet = worksheetPart.Worksheet; + // var columns = workSheet.Descendants().FirstOrDefault(); + // data.ColumnConfigurations = columns; + + // var comments = GetComments(worksheetPart); + // var sheetData = workSheet.Elements().First(); + + // rows = sheetData.Elements().ToList(); + + // if (rows.Count > 0) + // { + // var row = rows[0]; + // var cellEnumerator = GetExcelCellEnumerator(row); + // while (cellEnumerator.MoveNext()) + // { + // var cell = cellEnumerator.Current; + // var text = ReadExcelCell(cell, workbookPart).Trim(); + // data.Headers.Add(text); + // } + // } + + // // Read the sheet data + // if (rows.Count > 1) + // { + // for (var i = 1; i < rows.Count; i++) + // { + // var dataRow = new List(); + // data.DataRows.Add(dataRow); + // var row = rows[i]; + // var cellEnumerator = GetExcelCellEnumerator(row); + // while (cellEnumerator.MoveNext()) + // { + // var cell = cellEnumerator.Current; + // var text = ReadExcelCell(cell, workbookPart).Trim(); + // var patternFill = GetCellPatternFill(cell, workbookPart); + + // dataRow.Add(text); + // } + // } + // } + + // // Read the sheet PatternFill + // if (rows.Count > 1) + // { + // for (var i = 1; i < rows.Count; i++) + // { + // var dataRow = new List(); + // data.PatternFillRows.Add(dataRow); + // var row = rows[i]; + // var cellEnumerator = GetExcelCellEnumerator(row); + // while (cellEnumerator.MoveNext()) + // { + // var cell = cellEnumerator.Current; + // var patternFill = GetCellPatternFill(cell, workbookPart); + // dataRow.Add(patternFill); + // } + // } + // } + + // datas.Add(data); + // } + + + //} + //catch (Exception e) + //{ + // datas.First().Status.Message = "Unable to open the file"; + // return datas; + //} + + return datas; + + } + + + public ExcelData ReadExcel(IFormFile file) + { + var data = new ExcelData(); + + // Check if the file is excel + if (file.Length <= 0) + { + data.Status.Message = "You uploaded an empty file"; + return data; + } + + if (file.ContentType != "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + { + data.Status.Message = "Please upload a valid excel file of version 2007 and above"; + return data; + } + + // Open the excel document + WorkbookPart workbookPart; List rows; + try + { + using (Stream stream = file.OpenReadStream()) + { + return ReadExcel(stream); + } + } + catch (Exception e) + { + data.Status.Message = "Unable to open the file"; + return data; + } + } + + public ExcelData ReadExcel(Stream file,string sheetName) + { + var data = new ExcelData(); + + // Open the excel document + WorkbookPart workbookPart; List rows; + try + { + var document = SpreadsheetDocument.Open(file, false); + workbookPart = document.WorkbookPart; + + var sheets = workbookPart.Workbook.Descendants(); + var sheet = sheets.FirstOrDefault(x => x.Name == sheetName); + data = ReadSheet(workbookPart, sheet); + + return data; + } + catch (Exception e) + { + data.Status.Message = "Unable to open the file"; + return data; + } + } + + public void DeleteBlankRows(SheetData sheet) + { + + var rows = sheet.Elements().ToList(); + for (var rowIndex = 0; rowIndex < rows.Count; rowIndex++) + { + var row = rows[rowIndex]; + if (string.IsNullOrWhiteSpace(row.InnerText)) row.Remove(); + } + } + + public ExcelData ReadExcel(Stream file) + { + var data = new ExcelData(); + + // Open the excel document + WorkbookPart workbookPart; List rows; + try + { + var document = SpreadsheetDocument.Open(file, false); + workbookPart = document.WorkbookPart; + + var sheets = workbookPart.Workbook.Descendants(); + var sheet = sheets.FirstOrDefault(); + + data = ReadSheet(workbookPart, sheet); + + return data; + } + catch (Exception e) + { + data.Status.Message = "Unable to open the file"; + return data; + } + } + public IEnumerable GetComments(WorksheetPart sheet) + { + var result = new List(); + foreach (WorksheetCommentsPart commentsPart in sheet.GetPartsOfType()) + { + foreach (Comment comment in commentsPart.Comments.CommentList) + { + var newComment = new ExcelComment + { + Reference = comment.Reference, + Comment = comment.FirstChild.InnerText.Trim() + }; + result.Add(newComment); + } + } + + return result; + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Helpers.OpenXml/OpenXml/Excel/ExcelWriter.cs b/LibCommon/Kit.Helpers.OpenXml/OpenXml/Excel/ExcelWriter.cs new file mode 100644 index 0000000..5a5489f --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/OpenXml/Excel/ExcelWriter.cs @@ -0,0 +1,198 @@ +namespace Kit.Helpers +{ + using System; + using System.IO; + using DocumentFormat.OpenXml; + using DocumentFormat.OpenXml.Packaging; + using DocumentFormat.OpenXml.Spreadsheet; + using System.Collections.Generic; + using System.Data; + + public class ExcelWriter + { + private string ColumnLetter(int intCol) + { + var intFirstLetter = ((intCol) / 676) + 64; + var intSecondLetter = ((intCol % 676) / 26) + 64; + var intThirdLetter = (intCol % 26) + 65; + + var firstLetter = (intFirstLetter > 64) ? (char)intFirstLetter : ' '; + var secondLetter = (intSecondLetter > 64) ? (char)intSecondLetter : ' '; + var thirdLetter = (char)intThirdLetter; + + return string.Concat(firstLetter, secondLetter, thirdLetter).Trim(); + } + + private Cell CreateTextCell(string header, UInt32 index, string text) + { + var cell = new Cell + { + DataType = CellValues.InlineString, + CellReference = header + index + }; + + var istring = new InlineString(); + var t = new Text { Text = text }; + istring.AppendChild(t); + cell.AppendChild(istring); + return cell; + } + + public byte[] GenerateExcel(ExcelData data) + { + var stream = new MemoryStream(); + using (var document = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook)) + { + var workbookpart = document.AddWorkbookPart(); + workbookpart.Workbook = new Workbook(); + var worksheetPart = workbookpart.AddNewPart(); + var sheetData = new SheetData(); + + worksheetPart.Worksheet = new Worksheet(sheetData); + + var sheets = document.WorkbookPart.Workbook. + AppendChild(new Sheets()); + + var sheet = new Sheet() + { + Id = document.WorkbookPart.GetIdOfPart(worksheetPart), + SheetId = 1, + Name = data.SheetName ?? "Sheet 1" + }; + sheets.AppendChild(sheet); + + // Add header + UInt32 rowIdex = 0; + var row = new Row { RowIndex = ++rowIdex }; + sheetData.AppendChild(row); + var cellIdex = 0; + + foreach (var header in data.Headers) + { + row.AppendChild(CreateTextCell(ColumnLetter(cellIdex++), rowIdex, header ?? string.Empty)); + } + if (data.Headers.Count > 0) + { + // Add the column configuration if available + if (data.ColumnConfigurations != null) + { + var columns = (Columns)data.ColumnConfigurations.Clone(); + worksheetPart.Worksheet + .InsertAfter(columns, worksheetPart.Worksheet.SheetFormatProperties); + } + } + + // Add sheet data + foreach (var rowData in data.DataRows) + { + cellIdex = 0; + row = new Row { RowIndex = ++rowIdex }; + sheetData.AppendChild(row); + foreach (var callData in rowData) + { + var cell = CreateTextCell(ColumnLetter(cellIdex++), rowIdex, callData ?? string.Empty); + row.AppendChild(cell); + } + } + + workbookpart.Workbook.Save(); + document.Save(); + } + + return stream.ToArray(); + } + + public byte[] GenerateExcel(IEnumerable datas) + { + var stream = new MemoryStream(); + using (var document = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook)) + { + var workbookpart = document.AddWorkbookPart(); + workbookpart.Workbook = new Workbook(); + int pos = 0; + + + Sheets sheets = document.WorkbookPart.Workbook.AppendChild(new Sheets()); + UInt32Value sheetCount = 1; // Mimimum value is 1 + + foreach (var data in datas) + { + WorksheetPart worksheetPart = workbookpart.AddNewPart(); + worksheetPart.Worksheet = new Worksheet(new SheetData()); + + var sheetData = new SheetData(); + + worksheetPart.Worksheet = new Worksheet(sheetData); + var sheet = new Sheet() + { + Id = document.WorkbookPart.GetIdOfPart(worksheetPart), + SheetId = sheetCount, + Name = data.SheetName ?? $"Sheet {pos++}" + }; + sheets.AppendChild(sheet); + + sheetCount++; + // Add header + UInt32 rowIdex = 0; + var row = new Row { RowIndex = ++rowIdex }; + sheetData.AppendChild(row); + var cellIdex = 0; + + foreach (var header in data.Headers) + { + row.AppendChild(CreateTextCell(ColumnLetter(cellIdex++), rowIdex, header ?? string.Empty)); + } + if (data.Headers.Count > 0) + { + // Add the column configuration if available + if (data.ColumnConfigurations != null) + { + var columns = (Columns)data.ColumnConfigurations.Clone(); + worksheetPart.Worksheet + .InsertAfter(columns, worksheetPart.Worksheet.SheetFormatProperties); + } + } + + // Add sheet data + foreach (var rowData in data.DataRows) + { + cellIdex = 0; + row = new Row { RowIndex = ++rowIdex }; + sheetData.AppendChild(row); + foreach (var callData in rowData) + { + var cell = CreateTextCell(ColumnLetter(cellIdex++), rowIdex, callData ?? string.Empty); + row.AppendChild(cell); + } + } + } + + workbookpart.Workbook.Save(); + document.Save(); + } + + return stream.ToArray(); + } + + public byte[] GenerateExcel(DataTable dataTable, string sheetName = null) + { + var excelData = new ExcelData + { + Headers = new List(), + DataRows = new List>(), + SheetName = sheetName + }; + + foreach (DataColumn column in dataTable.Columns) excelData.Headers.Add(column.ColumnName); + + foreach (DataRow dataRow in dataTable.Rows) + { + var excelRow = new List(); + foreach (DataColumn column in dataTable.Columns) excelRow.Add(dataRow[column.ColumnName].ToString()); + excelData.DataRows.Add(excelRow); + } + + return GenerateExcel(excelData); + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Helpers.OpenXml/OpenXml/SLExcel/SLExcelData.cs b/LibCommon/Kit.Helpers.OpenXml/OpenXml/SLExcel/SLExcelData.cs new file mode 100644 index 0000000..49404b6 --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/OpenXml/SLExcel/SLExcelData.cs @@ -0,0 +1,30 @@ +namespace Kit.Helpers.OpenXml.SLExcel +{ + using System.Collections.Generic; + using DocumentFormat.OpenXml.Spreadsheet; + + public class SLExcelStatus + { + public string Message { get; set; } + public bool Success + { + get { return string.IsNullOrWhiteSpace(Message); } + } + } + + public class SLExcelData + { + public SLExcelStatus Status { get; set; } + public Columns ColumnConfigurations { get; set; } + public List Headers { get; set; } + public List> DataRows { get; set; } + public string SheetName { get; set; } + + public SLExcelData() + { + Status = new SLExcelStatus(); + Headers = new List(); + DataRows = new List>(); + } + } +} diff --git a/LibCommon/Kit.Helpers.OpenXml/OpenXml/SLExcel/SLExcelReader.cs b/LibCommon/Kit.Helpers.OpenXml/OpenXml/SLExcel/SLExcelReader.cs new file mode 100644 index 0000000..49e7626 --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/OpenXml/SLExcel/SLExcelReader.cs @@ -0,0 +1,414 @@ +using DocumentFormat.OpenXml.Packaging; +using DocumentFormat.OpenXml.Spreadsheet; +using Microsoft.AspNetCore.Http; +using System.Data; +using System.Text.RegularExpressions; + +namespace Kit.Helpers.OpenXml.SLExcel +{ + public interface IExcelReader + { + IEnumerable ReadAllExcel(IFormFile file); + SLExcelData ReadExcel(IFormFile file); + SLExcelData ReadExcel(Stream file, string sheetName, int headerIndex = 0, int? dataIdx = null); + SLExcelData ReadExcel(Stream file, string sheetName, string header, int? dataIdx = null); + bool IsExcel(Stream file); + } + + public class SLExcelReader : IExcelReader + { + private string GetColumnName(string cellReference) + { + var regex = new Regex("[A-Za-z]+"); + var match = regex.Match(cellReference); + + return match.Value; + } + + private int ConvertColumnNameToNumber(string columnName) + { + var alpha = new Regex("^[A-Z]+$"); + if (!alpha.IsMatch(columnName)) throw new ArgumentException(); + + char[] colLetters = columnName.ToCharArray(); + Array.Reverse(colLetters); + + var convertedValue = 0; + for (int i = 0; i < colLetters.Length; i++) + { + char letter = colLetters[i]; + int current = i == 0 ? letter - 65 : letter - 64; // ASCII 'A' = 65 + convertedValue += current * (int)Math.Pow(26, i); + } + + return convertedValue; + } + + private IEnumerator GetExcelCellEnumerator(Row row) + { + int currentCount = 0; + foreach (Cell cell in row.Descendants()) + { + string columnName = GetColumnName(cell.CellReference); + + int currentColumnIndex = ConvertColumnNameToNumber(columnName); + + for (; currentCount < currentColumnIndex; currentCount++) + { + var emptycell = new Cell() { DataType = null, CellValue = new CellValue(string.Empty) }; + yield return emptycell; + } + + yield return cell; + currentCount++; + } + } + + private string ReadExcelCell(Cell cell, SharedStringItem[] sharedStrings) + { + if (cell == null) + { + return string.Empty; + } + string text; + var cellValue = cell.CellValue; + if (cell.DataType != null && cell.DataType == CellValues.SharedString) + { + int index = Convert.ToInt32(cell.CellValue.Text); + SharedStringItem sharedString = sharedStrings.ElementAtOrDefault(index); + text = sharedString.InnerText; + } + else + { + text = (cellValue == null) ? cell.InnerText : cellValue.Text; + } + + return (text ?? string.Empty).Trim(); + } + + private SLExcelData ReadRowsAndSharedStrings(Stream file, string sheetName, out IList rows, out SharedStringItem[] sharedStrings) + { + var data = new SLExcelData(); + // Open the excel document + rows = new List(); + sharedStrings = new List().ToArray(); + try + { + var document = SpreadsheetDocument.Open(file, false); + WorkbookPart workbookPart = document.WorkbookPart; + + var sheets = workbookPart.Workbook.Descendants(); + var sheet = sheets.FirstOrDefault(x => x.Name == sheetName); + if (sheet == null) + { + data.Status.Message = $"В файле не найден лист \"{sheetName}\""; + return data; + } + + data.SheetName = sheet.Name; + + var workSheet = ((WorksheetPart)workbookPart.GetPartById(sheet.Id)).Worksheet; + var columns = workSheet.Descendants().FirstOrDefault(); + data.ColumnConfigurations = columns; + + var sheetData = workSheet.Elements().First(); + + rows = sheetData.Elements().ToList(); + + if (workbookPart.SharedStringTablePart?.SharedStringTable != null) + { + sharedStrings = workbookPart.SharedStringTablePart.SharedStringTable.Descendants().ToArray(); + } + } + catch (Exception e) + { + data.Status.Message = "Excel файл не прочитан"; + return data; + } + return data; + } + + private void FillSLExcelData(SLExcelData data, IList rows, SharedStringItem[] sharedStrings, int headerIndex, int? dataIdx) + { + // Read the header + if (rows.Count > headerIndex) + { + var row = rows[headerIndex]; + var cellEnumerator = GetExcelCellEnumerator(row); + while (cellEnumerator.MoveNext()) + { + var cell = cellEnumerator.Current; + var text = ReadExcelCell(cell, sharedStrings).Trim(); + data.Headers.Add(text); + } + } + int dataIndex = dataIdx.HasValue ? dataIdx.Value : headerIndex + 1; + // Read the sheet data + if (rows.Count > dataIndex) + { + for (var i = dataIndex; i < rows.Count; i++) + { + var dataRow = new List(); + data.DataRows.Add(dataRow); + var row = rows[i]; + var cellEnumerator = GetExcelCellEnumerator(row); + while (cellEnumerator.MoveNext()) + { + var cell = cellEnumerator.Current; + var text = ReadExcelCell(cell, sharedStrings).Trim(); + dataRow.Add(text); + } + if (data.ColumnConfigurations != null && dataRow.Count() < data.ColumnConfigurations.Count()) + { + int currentCount = dataRow.Count(); + for (; currentCount < data.ColumnConfigurations.Count(); currentCount++) + { + dataRow.Add(string.Empty); + } + } + } + } + } + + public IEnumerable ReadAllExcel(IFormFile file) + { + var datas = new List(); + + // Check if the file is excel + if (file.Length <= 0) + { + datas.First().Status.Message = "You uploaded an empty file"; + return datas; + } + + if (file.ContentType != "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + { + datas.First().Status.Message = "Please upload a valid excel file of version 2007 and above"; + return datas; + } + + // Open the excel document + List rows; + SharedStringItem[] sharedStrings = new List().ToArray(); + try + { + SpreadsheetDocument document; + using (Stream stream = file.OpenReadStream()) + { + document = SpreadsheetDocument.Open(stream, false); + } + WorkbookPart workbookPart = document.WorkbookPart; + + var sheets = workbookPart.Workbook.Descendants(); + + foreach (var sheet in sheets) + { + var data = new SLExcelData(); + data.SheetName = sheet.Name; + + var workSheet = ((WorksheetPart)workbookPart.GetPartById(sheet.Id)).Worksheet; + var columns = workSheet.Descendants().FirstOrDefault(); + data.ColumnConfigurations = columns; + + var sheetData = workSheet.Elements().First(); + + rows = sheetData.Elements().ToList(); + + if (workbookPart.SharedStringTablePart?.SharedStringTable != null) + { + sharedStrings = workbookPart.SharedStringTablePart.SharedStringTable.Descendants().ToArray(); + } + + if (rows.Count > 0) + { + var row = rows[0]; + var cellEnumerator = GetExcelCellEnumerator(row); + while (cellEnumerator.MoveNext()) + { + var cell = cellEnumerator.Current; + var text = ReadExcelCell(cell, sharedStrings).Trim(); + data.Headers.Add(text); + } + } + + // Read the sheet data + if (rows.Count > 1) + { + for (var i = 1; i < rows.Count; i++) + { + var dataRow = new List(); + data.DataRows.Add(dataRow); + var row = rows[i]; + var cellEnumerator = GetExcelCellEnumerator(row); + while (cellEnumerator.MoveNext()) + { + var cell = cellEnumerator.Current; + var text = ReadExcelCell(cell, sharedStrings).Trim(); + dataRow.Add(text); + } + } + } + + datas.Add(data); + } + + + } + catch (Exception e) + { + datas.First().Status.Message = "Unable to open the file"; + return datas; + } + + return datas; + + } + + public SLExcelData ReadExcel(IFormFile file) + { + var data = new SLExcelData(); + + // Check if the file is excel + if (file.Length <= 0) + { + data.Status.Message = "You uploaded an empty file"; + return data; + } + + if (file.ContentType != "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + { + data.Status.Message = "Please upload a valid excel file of version 2007 and above"; + return data; + } + + // Open the excel document + List rows; + SharedStringItem[] sharedStrings = new List().ToArray(); + try + { + SpreadsheetDocument document; + using (Stream stream = file.OpenReadStream()) + { + document = SpreadsheetDocument.Open(stream, false); + } + WorkbookPart workbookPart = document.WorkbookPart; + + var sheets = workbookPart.Workbook.Descendants(); + var sheet = sheets.First(); + data.SheetName = sheet.Name; + + var workSheet = ((WorksheetPart)workbookPart.GetPartById(sheet.Id)).Worksheet; + var columns = workSheet.Descendants().FirstOrDefault(); + data.ColumnConfigurations = columns; + + var sheetData = workSheet.Elements().First(); + + + rows = sheetData.Elements().ToList(); + + if (workbookPart.SharedStringTablePart?.SharedStringTable != null) + { + sharedStrings = workbookPart.SharedStringTablePart.SharedStringTable.Descendants().ToArray(); + } + } + catch (Exception e) + { + data.Status.Message = "Unable to open the file"; + return data; + } + + // Read the header + if (rows.Count > 0) + { + var row = rows[0]; + var cellEnumerator = GetExcelCellEnumerator(row); + while (cellEnumerator.MoveNext()) + { + var cell = cellEnumerator.Current; + var text = ReadExcelCell(cell, sharedStrings).Trim(); + data.Headers.Add(text); + } + } + + // Read the sheet data + if (rows.Count > 1) + { + for (var i = 1; i < rows.Count; i++) + { + var dataRow = new List(); + data.DataRows.Add(dataRow); + var row = rows[i]; + var cellEnumerator = GetExcelCellEnumerator(row); + while (cellEnumerator.MoveNext()) + { + var cell = cellEnumerator.Current; + var text = ReadExcelCell(cell, sharedStrings).Trim(); + dataRow.Add(text); + } + } + } + + return data; + } + + public SLExcelData ReadExcel(Stream file, string sheetName, int headerIndex = 0, int? dataIdx = null) + { + SLExcelData data = this.ReadRowsAndSharedStrings(file, sheetName, out IList rows, out SharedStringItem[] sharedStrings); + + this.FillSLExcelData(data, rows, sharedStrings, headerIndex, dataIdx); + + return data; + } + + public SLExcelData ReadExcel(Stream file, string sheetName, string header, int? dataIdx = null) + { + SLExcelData data = this.ReadRowsAndSharedStrings(file, sheetName, out IList rows, out SharedStringItem[] sharedStrings); + + int headerIndex = -1; + + for (int i = 0; i < rows.Count(); i++) + { + Row row = rows[i]; + var cellEnumerator = GetExcelCellEnumerator(row); + while (cellEnumerator.MoveNext()) + { + var cell = cellEnumerator.Current; + var text = ReadExcelCell(cell, sharedStrings).Trim(); + if (text.ToLowerEquals(header)) + { + headerIndex = i; + break; + } + } + if (headerIndex > -1) + { + break; + } + } + + if (headerIndex < 0) + { + data.Status.Message = $"На листе \"{sheetName}\" не найден заголовок {header}"; + return data; + } + + this.FillSLExcelData(data, rows, sharedStrings, headerIndex, dataIdx); + + return data; + } + + public bool IsExcel(Stream file) + { + try + { + using (var document = SpreadsheetDocument.Open(file, false)) ; + file.Position = 0; + return true; + } + catch + { + return false; + } + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Helpers.OpenXml/OpenXml/SLExcel/SLExcelWriter.cs b/LibCommon/Kit.Helpers.OpenXml/OpenXml/SLExcel/SLExcelWriter.cs new file mode 100644 index 0000000..196ea52 --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/OpenXml/SLExcel/SLExcelWriter.cs @@ -0,0 +1,196 @@ +namespace Kit.Helpers.OpenXml.SLExcel +{ + using System; + using System.IO; + using DocumentFormat.OpenXml; + using DocumentFormat.OpenXml.Packaging; + using DocumentFormat.OpenXml.Spreadsheet; + using System.Collections.Generic; + using System.Data; + + public class SLExcelWriter + { + public string ColumnLetter(int intCol) + { + var intFirstLetter = ((intCol) / 676) + 64; + var intSecondLetter = ((intCol % 676) / 26) + 64; + var intThirdLetter = (intCol % 26) + 65; + + var firstLetter = (intFirstLetter > 64) ? (char)intFirstLetter : ' '; + var secondLetter = (intSecondLetter > 64) ? (char)intSecondLetter : ' '; + var thirdLetter = (char)intThirdLetter; + + return string.Concat(firstLetter, secondLetter, thirdLetter).Trim(); + } + + public Cell CreateTextCell(string header, UInt32 index, string text) + { + var cell = new Cell + { + DataType = CellValues.InlineString, + CellReference = header + index + }; + + var istring = new InlineString(); + var t = new Text { Text = text }; + istring.AppendChild(t); + cell.AppendChild(istring); + return cell; + } + + public byte[] GenerateExcel(DataTable dataTable, string sheetName = null) + { + var excelData = new SLExcelData + { + Headers = new List(), + DataRows = new List>(), + SheetName = sheetName + }; + + foreach (DataColumn column in dataTable.Columns) excelData.Headers.Add(column.ColumnName); + + foreach (DataRow dataRow in dataTable.Rows) + { + var excelRow = new List(); + foreach (DataColumn column in dataTable.Columns) excelRow.Add(dataRow[column.ColumnName].ToString()); + excelData.DataRows.Add(excelRow); + } + + return GenerateExcel(excelData); + } + + public byte[] GenerateExcel(SLExcelData data) + { + var stream = new MemoryStream(); + using (var document = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook)) + { + var workbookpart = document.AddWorkbookPart(); + workbookpart.Workbook = new Workbook(); + var worksheetPart = workbookpart.AddNewPart(); + var sheetData = new SheetData(); + + worksheetPart.Worksheet = new Worksheet(sheetData); + + var sheets = document.WorkbookPart.Workbook. + AppendChild(new Sheets()); + + var sheet = new Sheet() + { + Id = document.WorkbookPart.GetIdOfPart(worksheetPart), + SheetId = 1, + Name = data.SheetName ?? "Sheet 1" + }; + sheets.AppendChild(sheet); + + // Add header + UInt32 rowIdex = 0; + var row = new Row { RowIndex = ++rowIdex }; + sheetData.AppendChild(row); + var cellIdex = 0; + + foreach (var header in data.Headers) + { + row.AppendChild(CreateTextCell(ColumnLetter(cellIdex++), rowIdex, header ?? string.Empty)); + } + if (data.Headers.Count > 0) + { + // Add the column configuration if available + if (data.ColumnConfigurations != null) + { + var columns = (Columns)data.ColumnConfigurations.Clone(); + worksheetPart.Worksheet + .InsertAfter(columns, worksheetPart.Worksheet.SheetFormatProperties); + } + } + + // Add sheet data + foreach (var rowData in data.DataRows) + { + cellIdex = 0; + row = new Row { RowIndex = ++rowIdex }; + sheetData.AppendChild(row); + foreach (var callData in rowData) + { + var cell = CreateTextCell(ColumnLetter(cellIdex++), rowIdex, callData ?? string.Empty); + row.AppendChild(cell); + } + } + + workbookpart.Workbook.Save(); + document.Save(); + } + return stream.ToArray(); + } + + public byte[] GenerateExcel(IEnumerable datas) + { + var stream = new MemoryStream(); + using (var document = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook)) + { + var workbookpart = document.AddWorkbookPart(); + workbookpart.Workbook = new Workbook(); + int pos = 0; + + + Sheets sheets = document.WorkbookPart.Workbook.AppendChild(new Sheets()); + UInt32Value sheetCount = 1; // Mimimum value is 1 + + foreach (var data in datas) + { + WorksheetPart worksheetPart = workbookpart.AddNewPart(); + worksheetPart.Worksheet = new Worksheet(new SheetData()); + + var sheetData = new SheetData(); + + worksheetPart.Worksheet = new Worksheet(sheetData); + var sheet = new Sheet() + { + Id = document.WorkbookPart.GetIdOfPart(worksheetPart), + SheetId = sheetCount, + Name = data.SheetName ?? $"Sheet {pos++}" + }; + sheets.AppendChild(sheet); + + sheetCount++; + // Add header + UInt32 rowIdex = 0; + var row = new Row { RowIndex = ++rowIdex }; + sheetData.AppendChild(row); + var cellIdex = 0; + + foreach (var header in data.Headers) + { + row.AppendChild(CreateTextCell(ColumnLetter(cellIdex++), rowIdex, header ?? string.Empty)); + } + if (data.Headers.Count > 0) + { + // Add the column configuration if available + if (data.ColumnConfigurations != null) + { + var columns = (Columns)data.ColumnConfigurations.Clone(); + worksheetPart.Worksheet + .InsertAfter(columns, worksheetPart.Worksheet.SheetFormatProperties); + } + } + + // Add sheet data + foreach (var rowData in data.DataRows) + { + cellIdex = 0; + row = new Row { RowIndex = ++rowIdex }; + sheetData.AppendChild(row); + foreach (var callData in rowData) + { + var cell = CreateTextCell(ColumnLetter(cellIdex++), rowIdex, callData ?? string.Empty); + row.AppendChild(cell); + } + } + } + + workbookpart.Workbook.Save(); + document.Save(); + } + return stream.ToArray(); + } + } +} diff --git a/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/Kit.Core.Helpers.dll b/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/Kit.Core.Helpers.dll new file mode 100644 index 0000000..5105b7c Binary files /dev/null and b/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/Kit.Core.Helpers.dll differ diff --git a/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/Kit.Core.Helpers.pdb b/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/Kit.Core.Helpers.pdb new file mode 100644 index 0000000..b92be39 Binary files /dev/null and b/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/Kit.Core.Helpers.pdb differ diff --git a/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/Kit.Helpers.OpenXml.deps.json b/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/Kit.Helpers.OpenXml.deps.json new file mode 100644 index 0000000..00fa067 --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/Kit.Helpers.OpenXml.deps.json @@ -0,0 +1,361 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v8.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v8.0": { + "Kit.Helpers.OpenXml/1.0.0": { + "dependencies": { + "DocumentFormat.OpenXml": "3.3.0", + "Kit.Core.Helpers": "1.0.0" + }, + "runtime": { + "Kit.Helpers.OpenXml.dll": {} + } + }, + "DocumentFormat.OpenXml/3.3.0": { + "dependencies": { + "DocumentFormat.OpenXml.Framework": "3.3.0" + }, + "runtime": { + "lib/net8.0/DocumentFormat.OpenXml.dll": { + "assemblyVersion": "3.3.0.0", + "fileVersion": "3.3.0.0" + } + } + }, + "DocumentFormat.OpenXml.Framework/3.3.0": { + "dependencies": { + "System.IO.Packaging": "8.0.1" + }, + "runtime": { + "lib/net8.0/DocumentFormat.OpenXml.Framework.dll": { + "assemblyVersion": "3.3.0.0", + "fileVersion": "3.3.0.0" + } + } + }, + "Microsoft.Data.Sqlite/9.0.5": { + "dependencies": { + "Microsoft.Data.Sqlite.Core": "9.0.5", + "SQLitePCLRaw.bundle_e_sqlite3": "2.1.10", + "SQLitePCLRaw.core": "2.1.10" + } + }, + "Microsoft.Data.Sqlite.Core/9.0.5": { + "dependencies": { + "SQLitePCLRaw.core": "2.1.10" + }, + "runtime": { + "lib/net8.0/Microsoft.Data.Sqlite.dll": { + "assemblyVersion": "9.0.5.0", + "fileVersion": "9.0.525.21604" + } + } + }, + "SQLitePCLRaw.bundle_e_sqlite3/2.1.10": { + "dependencies": { + "SQLitePCLRaw.lib.e_sqlite3": "2.1.10", + "SQLitePCLRaw.provider.e_sqlite3": "2.1.10" + }, + "runtime": { + "lib/netstandard2.0/SQLitePCLRaw.batteries_v2.dll": { + "assemblyVersion": "2.1.10.2445", + "fileVersion": "2.1.10.2445" + } + } + }, + "SQLitePCLRaw.core/2.1.10": { + "dependencies": { + "System.Memory": "4.5.3" + }, + "runtime": { + "lib/netstandard2.0/SQLitePCLRaw.core.dll": { + "assemblyVersion": "2.1.10.2445", + "fileVersion": "2.1.10.2445" + } + } + }, + "SQLitePCLRaw.lib.e_sqlite3/2.1.10": { + "runtimeTargets": { + "runtimes/browser-wasm/nativeassets/net8.0/e_sqlite3.a": { + "rid": "browser-wasm", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-arm/native/libe_sqlite3.so": { + "rid": "linux-arm", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-arm64/native/libe_sqlite3.so": { + "rid": "linux-arm64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-armel/native/libe_sqlite3.so": { + "rid": "linux-armel", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-mips64/native/libe_sqlite3.so": { + "rid": "linux-mips64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-musl-arm/native/libe_sqlite3.so": { + "rid": "linux-musl-arm", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-musl-arm64/native/libe_sqlite3.so": { + "rid": "linux-musl-arm64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-musl-s390x/native/libe_sqlite3.so": { + "rid": "linux-musl-s390x", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-musl-x64/native/libe_sqlite3.so": { + "rid": "linux-musl-x64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-ppc64le/native/libe_sqlite3.so": { + "rid": "linux-ppc64le", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-s390x/native/libe_sqlite3.so": { + "rid": "linux-s390x", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-x64/native/libe_sqlite3.so": { + "rid": "linux-x64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-x86/native/libe_sqlite3.so": { + "rid": "linux-x86", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/maccatalyst-arm64/native/libe_sqlite3.dylib": { + "rid": "maccatalyst-arm64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/maccatalyst-x64/native/libe_sqlite3.dylib": { + "rid": "maccatalyst-x64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/osx-arm64/native/libe_sqlite3.dylib": { + "rid": "osx-arm64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/osx-x64/native/libe_sqlite3.dylib": { + "rid": "osx-x64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/win-arm/native/e_sqlite3.dll": { + "rid": "win-arm", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/win-arm64/native/e_sqlite3.dll": { + "rid": "win-arm64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/win-x64/native/e_sqlite3.dll": { + "rid": "win-x64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/win-x86/native/e_sqlite3.dll": { + "rid": "win-x86", + "assetType": "native", + "fileVersion": "0.0.0.0" + } + } + }, + "SQLitePCLRaw.provider.e_sqlite3/2.1.10": { + "dependencies": { + "SQLitePCLRaw.core": "2.1.10" + }, + "runtime": { + "lib/net6.0/SQLitePCLRaw.provider.e_sqlite3.dll": { + "assemblyVersion": "2.1.10.2445", + "fileVersion": "2.1.10.2445" + } + } + }, + "System.CodeDom/9.0.5": { + "runtime": { + "lib/net8.0/System.CodeDom.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.525.21509" + } + } + }, + "System.IO.Packaging/8.0.1": { + "runtime": { + "lib/net8.0/System.IO.Packaging.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1024.46610" + } + } + }, + "System.Management/9.0.5": { + "dependencies": { + "System.CodeDom": "9.0.5" + }, + "runtime": { + "lib/net8.0/System.Management.dll": { + "assemblyVersion": "9.0.0.5", + "fileVersion": "9.0.525.21509" + } + }, + "runtimeTargets": { + "runtimes/win/lib/net8.0/System.Management.dll": { + "rid": "win", + "assetType": "runtime", + "assemblyVersion": "9.0.0.5", + "fileVersion": "9.0.525.21509" + } + } + }, + "System.Memory/4.5.3": {}, + "Kit.Core.Helpers/1.0.0": { + "dependencies": { + "Microsoft.Data.Sqlite": "9.0.5", + "Npgsql": "1.0.0", + "System.Management": "9.0.5" + }, + "runtime": { + "Kit.Core.Helpers.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + }, + "Npgsql/1.0.0": { + "runtime": { + "Npgsql.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + } + } + }, + "libraries": { + "Kit.Helpers.OpenXml/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "DocumentFormat.OpenXml/3.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-JogRPJNiE6kKvbuCqVRX691pPWeGMqdQgjrUwRYkdpfkMmtElfqAgcRR73geYj7OtBeEpstldZXXzJw27LUI9w==", + "path": "documentformat.openxml/3.3.0", + "hashPath": "documentformat.openxml.3.3.0.nupkg.sha512" + }, + "DocumentFormat.OpenXml.Framework/3.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-R5CLzEoeyr7XDB7g3NTxRobcU19agaxVAhGZm+fZUShJGiU4bw8oUgnA2BNFepigJckfFMayOBMAbV3kDXNInA==", + "path": "documentformat.openxml.framework/3.3.0", + "hashPath": "documentformat.openxml.framework.3.3.0.nupkg.sha512" + }, + "Microsoft.Data.Sqlite/9.0.5": { + "type": "package", + "serviceable": true, + "sha512": "sha512-Fht/vwX7uzPaIdlj1xtcpKD225GXjPOj7iW8934NNiTfBX5uqeCy2TdlPWPpLj+VPty/UmfgUSJSb55hXHqJMw==", + "path": "microsoft.data.sqlite/9.0.5", + "hashPath": "microsoft.data.sqlite.9.0.5.nupkg.sha512" + }, + "Microsoft.Data.Sqlite.Core/9.0.5": { + "type": "package", + "serviceable": true, + "sha512": "sha512-cP5eBSqra4Ae80X72g0h2N+jdrA4BgoMQmz9JaQmKAEXUHw9N21DPIBqIyMjOo2fK9ISiGytlAOxBAJf1hEvqg==", + "path": "microsoft.data.sqlite.core/9.0.5", + "hashPath": "microsoft.data.sqlite.core.9.0.5.nupkg.sha512" + }, + "SQLitePCLRaw.bundle_e_sqlite3/2.1.10": { + "type": "package", + "serviceable": true, + "sha512": "sha512-UxWuisvZ3uVcVOLJQv7urM/JiQH+v3TmaJc1BLKl5Dxfm/nTzTUrqswCqg/INiYLi61AXnHo1M1JPmPqqLnAdg==", + "path": "sqlitepclraw.bundle_e_sqlite3/2.1.10", + "hashPath": "sqlitepclraw.bundle_e_sqlite3.2.1.10.nupkg.sha512" + }, + "SQLitePCLRaw.core/2.1.10": { + "type": "package", + "serviceable": true, + "sha512": "sha512-Ii8JCbC7oiVclaE/mbDEK000EFIJ+ShRPwAvvV89GOZhQ+ZLtlnSWl6ksCNMKu/VGXA4Nfi2B7LhN/QFN9oBcw==", + "path": "sqlitepclraw.core/2.1.10", + "hashPath": "sqlitepclraw.core.2.1.10.nupkg.sha512" + }, + "SQLitePCLRaw.lib.e_sqlite3/2.1.10": { + "type": "package", + "serviceable": true, + "sha512": "sha512-mAr69tDbnf3QJpRy2nJz8Qdpebdil00fvycyByR58Cn9eARvR+UiG2Vzsp+4q1tV3ikwiYIjlXCQFc12GfebbA==", + "path": "sqlitepclraw.lib.e_sqlite3/2.1.10", + "hashPath": "sqlitepclraw.lib.e_sqlite3.2.1.10.nupkg.sha512" + }, + "SQLitePCLRaw.provider.e_sqlite3/2.1.10": { + "type": "package", + "serviceable": true, + "sha512": "sha512-uZVTi02C1SxqzgT0HqTWatIbWGb40iIkfc3FpFCpE/r7g6K0PqzDUeefL6P6HPhDtc6BacN3yQysfzP7ks+wSQ==", + "path": "sqlitepclraw.provider.e_sqlite3/2.1.10", + "hashPath": "sqlitepclraw.provider.e_sqlite3.2.1.10.nupkg.sha512" + }, + "System.CodeDom/9.0.5": { + "type": "package", + "serviceable": true, + "sha512": "sha512-cuzLM2MWutf9ZBEMPYYfd0DXwYdvntp7VCT6a/wvbKCa2ZuvGmW74xi+YBa2mrfEieAXqM4TNKlMmSnfAfpUoQ==", + "path": "system.codedom/9.0.5", + "hashPath": "system.codedom.9.0.5.nupkg.sha512" + }, + "System.IO.Packaging/8.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-KYkIOAvPexQOLDxPO2g0BVoWInnQhPpkFzRqvNrNrMhVT6kqhVr0zEb6KCHlptLFukxnZrjuMVAnxK7pOGUYrw==", + "path": "system.io.packaging/8.0.1", + "hashPath": "system.io.packaging.8.0.1.nupkg.sha512" + }, + "System.Management/9.0.5": { + "type": "package", + "serviceable": true, + "sha512": "sha512-n6o9PZm9p25+zAzC3/48K0oHnaPKTInRrxqFq1fi/5TPbMLjuoCm/h//mS3cUmSy+9AO1Z+qsC/Ilt/ZFatv5Q==", + "path": "system.management/9.0.5", + "hashPath": "system.management.9.0.5.nupkg.sha512" + }, + "System.Memory/4.5.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA==", + "path": "system.memory/4.5.3", + "hashPath": "system.memory.4.5.3.nupkg.sha512" + }, + "Kit.Core.Helpers/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "Npgsql/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/Kit.Helpers.OpenXml.dll b/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/Kit.Helpers.OpenXml.dll new file mode 100644 index 0000000..dc0b860 Binary files /dev/null and b/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/Kit.Helpers.OpenXml.dll differ diff --git a/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/Kit.Helpers.OpenXml.pdb b/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/Kit.Helpers.OpenXml.pdb new file mode 100644 index 0000000..4613d05 Binary files /dev/null and b/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/Kit.Helpers.OpenXml.pdb differ diff --git a/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/Npgsql.dll b/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/Npgsql.dll new file mode 100644 index 0000000..dab0131 Binary files /dev/null and b/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/Npgsql.dll differ diff --git a/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/Npgsql.pdb b/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/Npgsql.pdb new file mode 100644 index 0000000..4ab92eb Binary files /dev/null and b/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/Npgsql.pdb differ diff --git a/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/RiskProf.Helpers.OpenXml.deps.json b/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/RiskProf.Helpers.OpenXml.deps.json new file mode 100644 index 0000000..d7ab517 --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/RiskProf.Helpers.OpenXml.deps.json @@ -0,0 +1,361 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v8.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v8.0": { + "RiskProf.Helpers.OpenXml/1.0.0": { + "dependencies": { + "DocumentFormat.OpenXml": "3.3.0", + "Kit.Core.Helpers": "1.0.0" + }, + "runtime": { + "RiskProf.Helpers.OpenXml.dll": {} + } + }, + "DocumentFormat.OpenXml/3.3.0": { + "dependencies": { + "DocumentFormat.OpenXml.Framework": "3.3.0" + }, + "runtime": { + "lib/net8.0/DocumentFormat.OpenXml.dll": { + "assemblyVersion": "3.3.0.0", + "fileVersion": "3.3.0.0" + } + } + }, + "DocumentFormat.OpenXml.Framework/3.3.0": { + "dependencies": { + "System.IO.Packaging": "8.0.1" + }, + "runtime": { + "lib/net8.0/DocumentFormat.OpenXml.Framework.dll": { + "assemblyVersion": "3.3.0.0", + "fileVersion": "3.3.0.0" + } + } + }, + "Microsoft.Data.Sqlite/9.0.5": { + "dependencies": { + "Microsoft.Data.Sqlite.Core": "9.0.5", + "SQLitePCLRaw.bundle_e_sqlite3": "2.1.10", + "SQLitePCLRaw.core": "2.1.10" + } + }, + "Microsoft.Data.Sqlite.Core/9.0.5": { + "dependencies": { + "SQLitePCLRaw.core": "2.1.10" + }, + "runtime": { + "lib/net8.0/Microsoft.Data.Sqlite.dll": { + "assemblyVersion": "9.0.5.0", + "fileVersion": "9.0.525.21604" + } + } + }, + "SQLitePCLRaw.bundle_e_sqlite3/2.1.10": { + "dependencies": { + "SQLitePCLRaw.lib.e_sqlite3": "2.1.10", + "SQLitePCLRaw.provider.e_sqlite3": "2.1.10" + }, + "runtime": { + "lib/netstandard2.0/SQLitePCLRaw.batteries_v2.dll": { + "assemblyVersion": "2.1.10.2445", + "fileVersion": "2.1.10.2445" + } + } + }, + "SQLitePCLRaw.core/2.1.10": { + "dependencies": { + "System.Memory": "4.5.3" + }, + "runtime": { + "lib/netstandard2.0/SQLitePCLRaw.core.dll": { + "assemblyVersion": "2.1.10.2445", + "fileVersion": "2.1.10.2445" + } + } + }, + "SQLitePCLRaw.lib.e_sqlite3/2.1.10": { + "runtimeTargets": { + "runtimes/browser-wasm/nativeassets/net8.0/e_sqlite3.a": { + "rid": "browser-wasm", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-arm/native/libe_sqlite3.so": { + "rid": "linux-arm", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-arm64/native/libe_sqlite3.so": { + "rid": "linux-arm64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-armel/native/libe_sqlite3.so": { + "rid": "linux-armel", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-mips64/native/libe_sqlite3.so": { + "rid": "linux-mips64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-musl-arm/native/libe_sqlite3.so": { + "rid": "linux-musl-arm", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-musl-arm64/native/libe_sqlite3.so": { + "rid": "linux-musl-arm64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-musl-s390x/native/libe_sqlite3.so": { + "rid": "linux-musl-s390x", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-musl-x64/native/libe_sqlite3.so": { + "rid": "linux-musl-x64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-ppc64le/native/libe_sqlite3.so": { + "rid": "linux-ppc64le", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-s390x/native/libe_sqlite3.so": { + "rid": "linux-s390x", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-x64/native/libe_sqlite3.so": { + "rid": "linux-x64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/linux-x86/native/libe_sqlite3.so": { + "rid": "linux-x86", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/maccatalyst-arm64/native/libe_sqlite3.dylib": { + "rid": "maccatalyst-arm64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/maccatalyst-x64/native/libe_sqlite3.dylib": { + "rid": "maccatalyst-x64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/osx-arm64/native/libe_sqlite3.dylib": { + "rid": "osx-arm64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/osx-x64/native/libe_sqlite3.dylib": { + "rid": "osx-x64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/win-arm/native/e_sqlite3.dll": { + "rid": "win-arm", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/win-arm64/native/e_sqlite3.dll": { + "rid": "win-arm64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/win-x64/native/e_sqlite3.dll": { + "rid": "win-x64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/win-x86/native/e_sqlite3.dll": { + "rid": "win-x86", + "assetType": "native", + "fileVersion": "0.0.0.0" + } + } + }, + "SQLitePCLRaw.provider.e_sqlite3/2.1.10": { + "dependencies": { + "SQLitePCLRaw.core": "2.1.10" + }, + "runtime": { + "lib/net6.0/SQLitePCLRaw.provider.e_sqlite3.dll": { + "assemblyVersion": "2.1.10.2445", + "fileVersion": "2.1.10.2445" + } + } + }, + "System.CodeDom/9.0.5": { + "runtime": { + "lib/net8.0/System.CodeDom.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.525.21509" + } + } + }, + "System.IO.Packaging/8.0.1": { + "runtime": { + "lib/net8.0/System.IO.Packaging.dll": { + "assemblyVersion": "8.0.0.0", + "fileVersion": "8.0.1024.46610" + } + } + }, + "System.Management/9.0.5": { + "dependencies": { + "System.CodeDom": "9.0.5" + }, + "runtime": { + "lib/net8.0/System.Management.dll": { + "assemblyVersion": "9.0.0.5", + "fileVersion": "9.0.525.21509" + } + }, + "runtimeTargets": { + "runtimes/win/lib/net8.0/System.Management.dll": { + "rid": "win", + "assetType": "runtime", + "assemblyVersion": "9.0.0.5", + "fileVersion": "9.0.525.21509" + } + } + }, + "System.Memory/4.5.3": {}, + "Kit.Core.Helpers/1.0.0": { + "dependencies": { + "Microsoft.Data.Sqlite": "9.0.5", + "Npgsql": "1.0.0", + "System.Management": "9.0.5" + }, + "runtime": { + "Kit.Core.Helpers.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + }, + "Npgsql/1.0.0": { + "runtime": { + "Npgsql.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + } + } + }, + "libraries": { + "RiskProf.Helpers.OpenXml/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "DocumentFormat.OpenXml/3.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-JogRPJNiE6kKvbuCqVRX691pPWeGMqdQgjrUwRYkdpfkMmtElfqAgcRR73geYj7OtBeEpstldZXXzJw27LUI9w==", + "path": "documentformat.openxml/3.3.0", + "hashPath": "documentformat.openxml.3.3.0.nupkg.sha512" + }, + "DocumentFormat.OpenXml.Framework/3.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-R5CLzEoeyr7XDB7g3NTxRobcU19agaxVAhGZm+fZUShJGiU4bw8oUgnA2BNFepigJckfFMayOBMAbV3kDXNInA==", + "path": "documentformat.openxml.framework/3.3.0", + "hashPath": "documentformat.openxml.framework.3.3.0.nupkg.sha512" + }, + "Microsoft.Data.Sqlite/9.0.5": { + "type": "package", + "serviceable": true, + "sha512": "sha512-Fht/vwX7uzPaIdlj1xtcpKD225GXjPOj7iW8934NNiTfBX5uqeCy2TdlPWPpLj+VPty/UmfgUSJSb55hXHqJMw==", + "path": "microsoft.data.sqlite/9.0.5", + "hashPath": "microsoft.data.sqlite.9.0.5.nupkg.sha512" + }, + "Microsoft.Data.Sqlite.Core/9.0.5": { + "type": "package", + "serviceable": true, + "sha512": "sha512-cP5eBSqra4Ae80X72g0h2N+jdrA4BgoMQmz9JaQmKAEXUHw9N21DPIBqIyMjOo2fK9ISiGytlAOxBAJf1hEvqg==", + "path": "microsoft.data.sqlite.core/9.0.5", + "hashPath": "microsoft.data.sqlite.core.9.0.5.nupkg.sha512" + }, + "SQLitePCLRaw.bundle_e_sqlite3/2.1.10": { + "type": "package", + "serviceable": true, + "sha512": "sha512-UxWuisvZ3uVcVOLJQv7urM/JiQH+v3TmaJc1BLKl5Dxfm/nTzTUrqswCqg/INiYLi61AXnHo1M1JPmPqqLnAdg==", + "path": "sqlitepclraw.bundle_e_sqlite3/2.1.10", + "hashPath": "sqlitepclraw.bundle_e_sqlite3.2.1.10.nupkg.sha512" + }, + "SQLitePCLRaw.core/2.1.10": { + "type": "package", + "serviceable": true, + "sha512": "sha512-Ii8JCbC7oiVclaE/mbDEK000EFIJ+ShRPwAvvV89GOZhQ+ZLtlnSWl6ksCNMKu/VGXA4Nfi2B7LhN/QFN9oBcw==", + "path": "sqlitepclraw.core/2.1.10", + "hashPath": "sqlitepclraw.core.2.1.10.nupkg.sha512" + }, + "SQLitePCLRaw.lib.e_sqlite3/2.1.10": { + "type": "package", + "serviceable": true, + "sha512": "sha512-mAr69tDbnf3QJpRy2nJz8Qdpebdil00fvycyByR58Cn9eARvR+UiG2Vzsp+4q1tV3ikwiYIjlXCQFc12GfebbA==", + "path": "sqlitepclraw.lib.e_sqlite3/2.1.10", + "hashPath": "sqlitepclraw.lib.e_sqlite3.2.1.10.nupkg.sha512" + }, + "SQLitePCLRaw.provider.e_sqlite3/2.1.10": { + "type": "package", + "serviceable": true, + "sha512": "sha512-uZVTi02C1SxqzgT0HqTWatIbWGb40iIkfc3FpFCpE/r7g6K0PqzDUeefL6P6HPhDtc6BacN3yQysfzP7ks+wSQ==", + "path": "sqlitepclraw.provider.e_sqlite3/2.1.10", + "hashPath": "sqlitepclraw.provider.e_sqlite3.2.1.10.nupkg.sha512" + }, + "System.CodeDom/9.0.5": { + "type": "package", + "serviceable": true, + "sha512": "sha512-cuzLM2MWutf9ZBEMPYYfd0DXwYdvntp7VCT6a/wvbKCa2ZuvGmW74xi+YBa2mrfEieAXqM4TNKlMmSnfAfpUoQ==", + "path": "system.codedom/9.0.5", + "hashPath": "system.codedom.9.0.5.nupkg.sha512" + }, + "System.IO.Packaging/8.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-KYkIOAvPexQOLDxPO2g0BVoWInnQhPpkFzRqvNrNrMhVT6kqhVr0zEb6KCHlptLFukxnZrjuMVAnxK7pOGUYrw==", + "path": "system.io.packaging/8.0.1", + "hashPath": "system.io.packaging.8.0.1.nupkg.sha512" + }, + "System.Management/9.0.5": { + "type": "package", + "serviceable": true, + "sha512": "sha512-n6o9PZm9p25+zAzC3/48K0oHnaPKTInRrxqFq1fi/5TPbMLjuoCm/h//mS3cUmSy+9AO1Z+qsC/Ilt/ZFatv5Q==", + "path": "system.management/9.0.5", + "hashPath": "system.management.9.0.5.nupkg.sha512" + }, + "System.Memory/4.5.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA==", + "path": "system.memory/4.5.3", + "hashPath": "system.memory.4.5.3.nupkg.sha512" + }, + "Kit.Core.Helpers/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "Npgsql/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/RiskProf.Helpers.OpenXml.dll b/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/RiskProf.Helpers.OpenXml.dll new file mode 100644 index 0000000..34bc515 Binary files /dev/null and b/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/RiskProf.Helpers.OpenXml.dll differ diff --git a/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/RiskProf.Helpers.OpenXml.pdb b/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/RiskProf.Helpers.OpenXml.pdb new file mode 100644 index 0000000..5885b89 Binary files /dev/null and b/LibCommon/Kit.Helpers.OpenXml/bin/Debug/net8.0/RiskProf.Helpers.OpenXml.pdb differ diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs new file mode 100644 index 0000000..2217181 --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")] diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Help.1FB6692D.Up2Date b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Help.1FB6692D.Up2Date new file mode 100644 index 0000000..e69de29 diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.AssemblyInfo.cs b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.AssemblyInfo.cs new file mode 100644 index 0000000..7e350b0 --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.AssemblyInfo.cs @@ -0,0 +1,23 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("Kit.Helpers.OpenXml")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+37e709e30d2205dd559831f65211b8dd5d40a303")] +[assembly: System.Reflection.AssemblyProductAttribute("Kit.Helpers.OpenXml")] +[assembly: System.Reflection.AssemblyTitleAttribute("Kit.Helpers.OpenXml")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.AssemblyInfoInputs.cache b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.AssemblyInfoInputs.cache new file mode 100644 index 0000000..c8583f0 --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +fa1377e7cb8250e8c6ac39ddd0e6b5240f00202c7d361bdadf47ba6f85cd1ec9 diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.GeneratedMSBuildEditorConfig.editorconfig b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..6fec818 --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,15 @@ +is_global = true +build_property.TargetFramework = net8.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = Kit.Helpers.OpenXml +build_property.ProjectDir = C:\KIT\Kit.Core\LibCommon\Kit.Helpers.OpenXml\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 8.0 +build_property.EnableCodeStyleSeverity = diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.GlobalUsings.g.cs b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.GlobalUsings.g.cs new file mode 100644 index 0000000..8578f3d --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.GlobalUsings.g.cs @@ -0,0 +1,8 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.assets.cache b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.assets.cache new file mode 100644 index 0000000..2dbfc6c Binary files /dev/null and b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.assets.cache differ diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.csproj.AssemblyReference.cache b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.csproj.AssemblyReference.cache new file mode 100644 index 0000000..8d2481f Binary files /dev/null and b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.csproj.AssemblyReference.cache differ diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.csproj.CoreCompileInputs.cache b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..7f6e8da --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +14b0ba79093d88d5ec6003c00ac54cd14fec1f26aeb5cb5146942efc1c4379d9 diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.csproj.FileListAbsolute.txt b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..82d54d4 --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.csproj.FileListAbsolute.txt @@ -0,0 +1,17 @@ +C:\KIT\Kit.Core\LibCommon\Kit.Helpers.OpenXml\bin\Debug\net8.0\Kit.Helpers.OpenXml.deps.json +C:\KIT\Kit.Core\LibCommon\Kit.Helpers.OpenXml\bin\Debug\net8.0\Kit.Helpers.OpenXml.dll +C:\KIT\Kit.Core\LibCommon\Kit.Helpers.OpenXml\bin\Debug\net8.0\Kit.Helpers.OpenXml.pdb +C:\KIT\Kit.Core\LibCommon\Kit.Helpers.OpenXml\bin\Debug\net8.0\Kit.Core.Helpers.dll +C:\KIT\Kit.Core\LibCommon\Kit.Helpers.OpenXml\bin\Debug\net8.0\Npgsql.dll +C:\KIT\Kit.Core\LibCommon\Kit.Helpers.OpenXml\bin\Debug\net8.0\Kit.Core.Helpers.pdb +C:\KIT\Kit.Core\LibCommon\Kit.Helpers.OpenXml\bin\Debug\net8.0\Npgsql.pdb +C:\KIT\Kit.Core\LibCommon\Kit.Helpers.OpenXml\obj\Debug\net8.0\Kit.Helpers.OpenXml.csproj.AssemblyReference.cache +C:\KIT\Kit.Core\LibCommon\Kit.Helpers.OpenXml\obj\Debug\net8.0\Kit.Helpers.OpenXml.GeneratedMSBuildEditorConfig.editorconfig +C:\KIT\Kit.Core\LibCommon\Kit.Helpers.OpenXml\obj\Debug\net8.0\Kit.Helpers.OpenXml.AssemblyInfoInputs.cache +C:\KIT\Kit.Core\LibCommon\Kit.Helpers.OpenXml\obj\Debug\net8.0\Kit.Helpers.OpenXml.AssemblyInfo.cs +C:\KIT\Kit.Core\LibCommon\Kit.Helpers.OpenXml\obj\Debug\net8.0\Kit.Helpers.OpenXml.csproj.CoreCompileInputs.cache +C:\KIT\Kit.Core\LibCommon\Kit.Helpers.OpenXml\obj\Debug\net8.0\Kit.Help.1FB6692D.Up2Date +C:\KIT\Kit.Core\LibCommon\Kit.Helpers.OpenXml\obj\Debug\net8.0\Kit.Helpers.OpenXml.dll +C:\KIT\Kit.Core\LibCommon\Kit.Helpers.OpenXml\obj\Debug\net8.0\refint\Kit.Helpers.OpenXml.dll +C:\KIT\Kit.Core\LibCommon\Kit.Helpers.OpenXml\obj\Debug\net8.0\Kit.Helpers.OpenXml.pdb +C:\KIT\Kit.Core\LibCommon\Kit.Helpers.OpenXml\obj\Debug\net8.0\ref\Kit.Helpers.OpenXml.dll diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.dll b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.dll new file mode 100644 index 0000000..dc0b860 Binary files /dev/null and b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.dll differ diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.pdb b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.pdb new file mode 100644 index 0000000..4613d05 Binary files /dev/null and b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/Kit.Helpers.OpenXml.pdb differ diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.E68B3E43.Up2Date b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.E68B3E43.Up2Date new file mode 100644 index 0000000..e69de29 diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.AssemblyInfo.cs b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.AssemblyInfo.cs new file mode 100644 index 0000000..0d19501 --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.AssemblyInfo.cs @@ -0,0 +1,23 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("RiskProf.Helpers.OpenXml")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+37e709e30d2205dd559831f65211b8dd5d40a303")] +[assembly: System.Reflection.AssemblyProductAttribute("RiskProf.Helpers.OpenXml")] +[assembly: System.Reflection.AssemblyTitleAttribute("RiskProf.Helpers.OpenXml")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.AssemblyInfoInputs.cache b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.AssemblyInfoInputs.cache new file mode 100644 index 0000000..1330a56 --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +d5669eb22360845bfc494b75edf7d1acd1c34d08ab007d685086c41ae514d13b diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.GeneratedMSBuildEditorConfig.editorconfig b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..b96eb7e --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,15 @@ +is_global = true +build_property.TargetFramework = net8.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = RiskProf.Helpers.OpenXml +build_property.ProjectDir = C:\KIT\Kit.Core\LibCommon\RiskProf.Helpers.OpenXml\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 8.0 +build_property.EnableCodeStyleSeverity = diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.GlobalUsings.g.cs b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.GlobalUsings.g.cs new file mode 100644 index 0000000..8578f3d --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.GlobalUsings.g.cs @@ -0,0 +1,8 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.assets.cache b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.assets.cache new file mode 100644 index 0000000..a9bb4f7 Binary files /dev/null and b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.assets.cache differ diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.csproj.AssemblyReference.cache b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.csproj.AssemblyReference.cache new file mode 100644 index 0000000..2aacd07 Binary files /dev/null and b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.csproj.AssemblyReference.cache differ diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.csproj.CoreCompileInputs.cache b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..877c17b --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +487c11bc5f198d36f08561491e8dfb6f23b1b3b6ffc8468b59e6d199bf748895 diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.csproj.FileListAbsolute.txt b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..c4dd312 --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.csproj.FileListAbsolute.txt @@ -0,0 +1,17 @@ +C:\KIT\Kit.Core\LibCommon\RiskProf.Helpers.OpenXml\bin\Debug\net8.0\RiskProf.Helpers.OpenXml.deps.json +C:\KIT\Kit.Core\LibCommon\RiskProf.Helpers.OpenXml\bin\Debug\net8.0\RiskProf.Helpers.OpenXml.dll +C:\KIT\Kit.Core\LibCommon\RiskProf.Helpers.OpenXml\bin\Debug\net8.0\RiskProf.Helpers.OpenXml.pdb +C:\KIT\Kit.Core\LibCommon\RiskProf.Helpers.OpenXml\bin\Debug\net8.0\Kit.Core.Helpers.dll +C:\KIT\Kit.Core\LibCommon\RiskProf.Helpers.OpenXml\bin\Debug\net8.0\Npgsql.dll +C:\KIT\Kit.Core\LibCommon\RiskProf.Helpers.OpenXml\bin\Debug\net8.0\Kit.Core.Helpers.pdb +C:\KIT\Kit.Core\LibCommon\RiskProf.Helpers.OpenXml\bin\Debug\net8.0\Npgsql.pdb +C:\KIT\Kit.Core\LibCommon\RiskProf.Helpers.OpenXml\obj\Debug\net8.0\RiskProf.Helpers.OpenXml.csproj.AssemblyReference.cache +C:\KIT\Kit.Core\LibCommon\RiskProf.Helpers.OpenXml\obj\Debug\net8.0\RiskProf.Helpers.OpenXml.GeneratedMSBuildEditorConfig.editorconfig +C:\KIT\Kit.Core\LibCommon\RiskProf.Helpers.OpenXml\obj\Debug\net8.0\RiskProf.Helpers.OpenXml.AssemblyInfoInputs.cache +C:\KIT\Kit.Core\LibCommon\RiskProf.Helpers.OpenXml\obj\Debug\net8.0\RiskProf.Helpers.OpenXml.AssemblyInfo.cs +C:\KIT\Kit.Core\LibCommon\RiskProf.Helpers.OpenXml\obj\Debug\net8.0\RiskProf.Helpers.OpenXml.csproj.CoreCompileInputs.cache +C:\KIT\Kit.Core\LibCommon\RiskProf.Helpers.OpenXml\obj\Debug\net8.0\RiskProf.E68B3E43.Up2Date +C:\KIT\Kit.Core\LibCommon\RiskProf.Helpers.OpenXml\obj\Debug\net8.0\RiskProf.Helpers.OpenXml.dll +C:\KIT\Kit.Core\LibCommon\RiskProf.Helpers.OpenXml\obj\Debug\net8.0\refint\RiskProf.Helpers.OpenXml.dll +C:\KIT\Kit.Core\LibCommon\RiskProf.Helpers.OpenXml\obj\Debug\net8.0\RiskProf.Helpers.OpenXml.pdb +C:\KIT\Kit.Core\LibCommon\RiskProf.Helpers.OpenXml\obj\Debug\net8.0\ref\RiskProf.Helpers.OpenXml.dll diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.dll b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.dll new file mode 100644 index 0000000..34bc515 Binary files /dev/null and b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.dll differ diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.pdb b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.pdb new file mode 100644 index 0000000..5885b89 Binary files /dev/null and b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/RiskProf.Helpers.OpenXml.pdb differ diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/ref/Kit.Helpers.OpenXml.dll b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/ref/Kit.Helpers.OpenXml.dll new file mode 100644 index 0000000..b1d6694 Binary files /dev/null and b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/ref/Kit.Helpers.OpenXml.dll differ diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/ref/RiskProf.Helpers.OpenXml.dll b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/ref/RiskProf.Helpers.OpenXml.dll new file mode 100644 index 0000000..422a27d Binary files /dev/null and b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/ref/RiskProf.Helpers.OpenXml.dll differ diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/refint/Kit.Helpers.OpenXml.dll b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/refint/Kit.Helpers.OpenXml.dll new file mode 100644 index 0000000..b1d6694 Binary files /dev/null and b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/refint/Kit.Helpers.OpenXml.dll differ diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/refint/RiskProf.Helpers.OpenXml.dll b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/refint/RiskProf.Helpers.OpenXml.dll new file mode 100644 index 0000000..422a27d Binary files /dev/null and b/LibCommon/Kit.Helpers.OpenXml/obj/Debug/net8.0/refint/RiskProf.Helpers.OpenXml.dll differ diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Kit.Helpers.OpenXml.csproj.nuget.dgspec.json b/LibCommon/Kit.Helpers.OpenXml/obj/Kit.Helpers.OpenXml.csproj.nuget.dgspec.json new file mode 100644 index 0000000..bd5016d --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/obj/Kit.Helpers.OpenXml.csproj.nuget.dgspec.json @@ -0,0 +1,236 @@ +{ + "format": 1, + "restore": { + "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Helpers.OpenXml\\Kit.Helpers.OpenXml.csproj": {} + }, + "projects": { + "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj", + "projectName": "Kit.Core.Helpers", + "projectPath": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj", + "packagesPath": "C:\\Users\\user\\.nuget\\packages\\", + "outputPath": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Core.Helpers\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\user\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "C:\\GIT\\RiskProf.Modules.Core\\RiskProf.LK.Back\\RiskProf.LK.Back\\packages": {}, + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": { + "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\Npgsql.csproj": { + "projectPath": "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\Npgsql.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "dependencies": { + "Microsoft.Data.Sqlite": { + "target": "Package", + "version": "[9.0.5, )" + }, + "System.Management": { + "target": "Package", + "version": "[9.0.5, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.302/PortableRuntimeIdentifierGraph.json" + } + } + }, + "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Helpers.OpenXml\\Kit.Helpers.OpenXml.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Helpers.OpenXml\\Kit.Helpers.OpenXml.csproj", + "projectName": "Kit.Helpers.OpenXml", + "projectPath": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Helpers.OpenXml\\Kit.Helpers.OpenXml.csproj", + "packagesPath": "C:\\Users\\user\\.nuget\\packages\\", + "outputPath": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Helpers.OpenXml\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\user\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "C:\\GIT\\RiskProf.Modules.Core\\RiskProf.LK.Back\\RiskProf.LK.Back\\packages": {}, + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": { + "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj": { + "projectPath": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "dependencies": { + "DocumentFormat.OpenXml": { + "target": "Package", + "version": "[3.3.0, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.302/PortableRuntimeIdentifierGraph.json" + } + } + }, + "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\Npgsql.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\Npgsql.csproj", + "projectName": "Npgsql", + "projectPath": "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\Npgsql.csproj", + "packagesPath": "C:\\Users\\user\\.nuget\\packages\\", + "outputPath": "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\user\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "C:\\GIT\\RiskProf.Modules.Core\\RiskProf.LK.Back\\RiskProf.LK.Back\\packages": {}, + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.302/PortableRuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Kit.Helpers.OpenXml.csproj.nuget.g.props b/LibCommon/Kit.Helpers.OpenXml/obj/Kit.Helpers.OpenXml.csproj.nuget.g.props new file mode 100644 index 0000000..020441f --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/obj/Kit.Helpers.OpenXml.csproj.nuget.g.props @@ -0,0 +1,16 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\user\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages + PackageReference + 6.14.0 + + + + + + \ No newline at end of file diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/Kit.Helpers.OpenXml.csproj.nuget.g.targets b/LibCommon/Kit.Helpers.OpenXml/obj/Kit.Helpers.OpenXml.csproj.nuget.g.targets new file mode 100644 index 0000000..ce16130 --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/obj/Kit.Helpers.OpenXml.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/RiskProf.Helpers.OpenXml.csproj.nuget.dgspec.json b/LibCommon/Kit.Helpers.OpenXml/obj/RiskProf.Helpers.OpenXml.csproj.nuget.dgspec.json new file mode 100644 index 0000000..d2a5367 --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/obj/RiskProf.Helpers.OpenXml.csproj.nuget.dgspec.json @@ -0,0 +1,236 @@ +{ + "format": 1, + "restore": { + "C:\\KIT\\Kit.Core\\LibCommon\\RiskProf.Helpers.OpenXml\\RiskProf.Helpers.OpenXml.csproj": {} + }, + "projects": { + "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj", + "projectName": "Kit.Core.Helpers", + "projectPath": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj", + "packagesPath": "C:\\Users\\user\\.nuget\\packages\\", + "outputPath": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Core.Helpers\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\user\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "C:\\GIT\\RiskProf.Modules.Core\\RiskProf.LK.Back\\RiskProf.LK.Back\\packages": {}, + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": { + "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\Npgsql.csproj": { + "projectPath": "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\Npgsql.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "dependencies": { + "Microsoft.Data.Sqlite": { + "target": "Package", + "version": "[9.0.5, )" + }, + "System.Management": { + "target": "Package", + "version": "[9.0.5, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.302/PortableRuntimeIdentifierGraph.json" + } + } + }, + "C:\\KIT\\Kit.Core\\LibCommon\\RiskProf.Helpers.OpenXml\\RiskProf.Helpers.OpenXml.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\KIT\\Kit.Core\\LibCommon\\RiskProf.Helpers.OpenXml\\RiskProf.Helpers.OpenXml.csproj", + "projectName": "RiskProf.Helpers.OpenXml", + "projectPath": "C:\\KIT\\Kit.Core\\LibCommon\\RiskProf.Helpers.OpenXml\\RiskProf.Helpers.OpenXml.csproj", + "packagesPath": "C:\\Users\\user\\.nuget\\packages\\", + "outputPath": "C:\\KIT\\Kit.Core\\LibCommon\\RiskProf.Helpers.OpenXml\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\user\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "C:\\GIT\\RiskProf.Modules.Core\\RiskProf.LK.Back\\RiskProf.LK.Back\\packages": {}, + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": { + "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj": { + "projectPath": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "dependencies": { + "DocumentFormat.OpenXml": { + "target": "Package", + "version": "[3.3.0, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.302/PortableRuntimeIdentifierGraph.json" + } + } + }, + "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\Npgsql.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\Npgsql.csproj", + "projectName": "Npgsql", + "projectPath": "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\Npgsql.csproj", + "packagesPath": "C:\\Users\\user\\.nuget\\packages\\", + "outputPath": "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\user\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "C:\\GIT\\RiskProf.Modules.Core\\RiskProf.LK.Back\\RiskProf.LK.Back\\packages": {}, + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.302/PortableRuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/RiskProf.Helpers.OpenXml.csproj.nuget.g.props b/LibCommon/Kit.Helpers.OpenXml/obj/RiskProf.Helpers.OpenXml.csproj.nuget.g.props new file mode 100644 index 0000000..020441f --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/obj/RiskProf.Helpers.OpenXml.csproj.nuget.g.props @@ -0,0 +1,16 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\user\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages + PackageReference + 6.14.0 + + + + + + \ No newline at end of file diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/RiskProf.Helpers.OpenXml.csproj.nuget.g.targets b/LibCommon/Kit.Helpers.OpenXml/obj/RiskProf.Helpers.OpenXml.csproj.nuget.g.targets new file mode 100644 index 0000000..ce16130 --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/obj/RiskProf.Helpers.OpenXml.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/project.assets.json b/LibCommon/Kit.Helpers.OpenXml/obj/project.assets.json new file mode 100644 index 0000000..9c02a52 --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/obj/project.assets.json @@ -0,0 +1,679 @@ +{ + "version": 3, + "targets": { + "net8.0": { + "DocumentFormat.OpenXml/3.3.0": { + "type": "package", + "dependencies": { + "DocumentFormat.OpenXml.Framework": "3.3.0" + }, + "compile": { + "lib/net8.0/DocumentFormat.OpenXml.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/DocumentFormat.OpenXml.dll": { + "related": ".xml" + } + } + }, + "DocumentFormat.OpenXml.Framework/3.3.0": { + "type": "package", + "dependencies": { + "System.IO.Packaging": "8.0.1" + }, + "compile": { + "lib/net8.0/DocumentFormat.OpenXml.Framework.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/DocumentFormat.OpenXml.Framework.dll": { + "related": ".xml" + } + } + }, + "Microsoft.Data.Sqlite/9.0.5": { + "type": "package", + "dependencies": { + "Microsoft.Data.Sqlite.Core": "9.0.5", + "SQLitePCLRaw.bundle_e_sqlite3": "2.1.10", + "SQLitePCLRaw.core": "2.1.10" + }, + "compile": { + "lib/netstandard2.0/_._": {} + }, + "runtime": { + "lib/netstandard2.0/_._": {} + } + }, + "Microsoft.Data.Sqlite.Core/9.0.5": { + "type": "package", + "dependencies": { + "SQLitePCLRaw.core": "2.1.10" + }, + "compile": { + "lib/net8.0/Microsoft.Data.Sqlite.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Microsoft.Data.Sqlite.dll": { + "related": ".xml" + } + } + }, + "SQLitePCLRaw.bundle_e_sqlite3/2.1.10": { + "type": "package", + "dependencies": { + "SQLitePCLRaw.lib.e_sqlite3": "2.1.10", + "SQLitePCLRaw.provider.e_sqlite3": "2.1.10" + }, + "compile": { + "lib/netstandard2.0/SQLitePCLRaw.batteries_v2.dll": {} + }, + "runtime": { + "lib/netstandard2.0/SQLitePCLRaw.batteries_v2.dll": {} + } + }, + "SQLitePCLRaw.core/2.1.10": { + "type": "package", + "dependencies": { + "System.Memory": "4.5.3" + }, + "compile": { + "lib/netstandard2.0/SQLitePCLRaw.core.dll": {} + }, + "runtime": { + "lib/netstandard2.0/SQLitePCLRaw.core.dll": {} + } + }, + "SQLitePCLRaw.lib.e_sqlite3/2.1.10": { + "type": "package", + "compile": { + "lib/netstandard2.0/_._": {} + }, + "runtime": { + "lib/netstandard2.0/_._": {} + }, + "build": { + "buildTransitive/net8.0/SQLitePCLRaw.lib.e_sqlite3.targets": {} + }, + "runtimeTargets": { + "runtimes/browser-wasm/nativeassets/net8.0/e_sqlite3.a": { + "assetType": "native", + "rid": "browser-wasm" + }, + "runtimes/linux-arm/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-arm" + }, + "runtimes/linux-arm64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-arm64" + }, + "runtimes/linux-armel/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-armel" + }, + "runtimes/linux-mips64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-mips64" + }, + "runtimes/linux-musl-arm/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-musl-arm" + }, + "runtimes/linux-musl-arm64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-musl-arm64" + }, + "runtimes/linux-musl-s390x/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-musl-s390x" + }, + "runtimes/linux-musl-x64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-musl-x64" + }, + "runtimes/linux-ppc64le/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-ppc64le" + }, + "runtimes/linux-s390x/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-s390x" + }, + "runtimes/linux-x64/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-x64" + }, + "runtimes/linux-x86/native/libe_sqlite3.so": { + "assetType": "native", + "rid": "linux-x86" + }, + "runtimes/maccatalyst-arm64/native/libe_sqlite3.dylib": { + "assetType": "native", + "rid": "maccatalyst-arm64" + }, + "runtimes/maccatalyst-x64/native/libe_sqlite3.dylib": { + "assetType": "native", + "rid": "maccatalyst-x64" + }, + "runtimes/osx-arm64/native/libe_sqlite3.dylib": { + "assetType": "native", + "rid": "osx-arm64" + }, + "runtimes/osx-x64/native/libe_sqlite3.dylib": { + "assetType": "native", + "rid": "osx-x64" + }, + "runtimes/win-arm/native/e_sqlite3.dll": { + "assetType": "native", + "rid": "win-arm" + }, + "runtimes/win-arm64/native/e_sqlite3.dll": { + "assetType": "native", + "rid": "win-arm64" + }, + "runtimes/win-x64/native/e_sqlite3.dll": { + "assetType": "native", + "rid": "win-x64" + }, + "runtimes/win-x86/native/e_sqlite3.dll": { + "assetType": "native", + "rid": "win-x86" + } + } + }, + "SQLitePCLRaw.provider.e_sqlite3/2.1.10": { + "type": "package", + "dependencies": { + "SQLitePCLRaw.core": "2.1.10" + }, + "compile": { + "lib/net6.0/SQLitePCLRaw.provider.e_sqlite3.dll": {} + }, + "runtime": { + "lib/net6.0/SQLitePCLRaw.provider.e_sqlite3.dll": {} + } + }, + "System.CodeDom/9.0.5": { + "type": "package", + "compile": { + "lib/net8.0/System.CodeDom.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/System.CodeDom.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "System.IO.Packaging/8.0.1": { + "type": "package", + "compile": { + "lib/net8.0/System.IO.Packaging.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/System.IO.Packaging.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, + "System.Management/9.0.5": { + "type": "package", + "dependencies": { + "System.CodeDom": "9.0.5" + }, + "compile": { + "lib/net8.0/System.Management.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/System.Management.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + }, + "runtimeTargets": { + "runtimes/win/lib/net8.0/System.Management.dll": { + "assetType": "runtime", + "rid": "win" + } + } + }, + "System.Memory/4.5.3": { + "type": "package", + "compile": { + "ref/netcoreapp2.1/_._": {} + }, + "runtime": { + "lib/netcoreapp2.1/_._": {} + } + }, + "Kit.Core.Helpers/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v8.0", + "dependencies": { + "Microsoft.Data.Sqlite": "9.0.5", + "Npgsql": "1.0.0", + "System.Management": "9.0.5" + }, + "compile": { + "bin/placeholder/Kit.Core.Helpers.dll": {} + }, + "runtime": { + "bin/placeholder/Kit.Core.Helpers.dll": {} + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "Npgsql/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v8.0", + "compile": { + "bin/placeholder/Npgsql.dll": {} + }, + "runtime": { + "bin/placeholder/Npgsql.dll": {} + } + } + } + }, + "libraries": { + "DocumentFormat.OpenXml/3.3.0": { + "sha512": "JogRPJNiE6kKvbuCqVRX691pPWeGMqdQgjrUwRYkdpfkMmtElfqAgcRR73geYj7OtBeEpstldZXXzJw27LUI9w==", + "type": "package", + "path": "documentformat.openxml/3.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "documentformat.openxml.3.3.0.nupkg.sha512", + "documentformat.openxml.nuspec", + "icon.png", + "lib/net35/DocumentFormat.OpenXml.dll", + "lib/net35/DocumentFormat.OpenXml.xml", + "lib/net40/DocumentFormat.OpenXml.dll", + "lib/net40/DocumentFormat.OpenXml.xml", + "lib/net46/DocumentFormat.OpenXml.dll", + "lib/net46/DocumentFormat.OpenXml.xml", + "lib/net8.0/DocumentFormat.OpenXml.dll", + "lib/net8.0/DocumentFormat.OpenXml.xml", + "lib/netstandard2.0/DocumentFormat.OpenXml.dll", + "lib/netstandard2.0/DocumentFormat.OpenXml.xml" + ] + }, + "DocumentFormat.OpenXml.Framework/3.3.0": { + "sha512": "R5CLzEoeyr7XDB7g3NTxRobcU19agaxVAhGZm+fZUShJGiU4bw8oUgnA2BNFepigJckfFMayOBMAbV3kDXNInA==", + "type": "package", + "path": "documentformat.openxml.framework/3.3.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "documentformat.openxml.framework.3.3.0.nupkg.sha512", + "documentformat.openxml.framework.nuspec", + "icon.png", + "lib/net35/DocumentFormat.OpenXml.Framework.dll", + "lib/net35/DocumentFormat.OpenXml.Framework.xml", + "lib/net40/DocumentFormat.OpenXml.Framework.dll", + "lib/net40/DocumentFormat.OpenXml.Framework.xml", + "lib/net46/DocumentFormat.OpenXml.Framework.dll", + "lib/net46/DocumentFormat.OpenXml.Framework.xml", + "lib/net6.0/DocumentFormat.OpenXml.Framework.dll", + "lib/net6.0/DocumentFormat.OpenXml.Framework.xml", + "lib/net8.0/DocumentFormat.OpenXml.Framework.dll", + "lib/net8.0/DocumentFormat.OpenXml.Framework.xml", + "lib/netstandard2.0/DocumentFormat.OpenXml.Framework.dll", + "lib/netstandard2.0/DocumentFormat.OpenXml.Framework.xml" + ] + }, + "Microsoft.Data.Sqlite/9.0.5": { + "sha512": "Fht/vwX7uzPaIdlj1xtcpKD225GXjPOj7iW8934NNiTfBX5uqeCy2TdlPWPpLj+VPty/UmfgUSJSb55hXHqJMw==", + "type": "package", + "path": "microsoft.data.sqlite/9.0.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/netstandard2.0/_._", + "microsoft.data.sqlite.9.0.5.nupkg.sha512", + "microsoft.data.sqlite.nuspec" + ] + }, + "Microsoft.Data.Sqlite.Core/9.0.5": { + "sha512": "cP5eBSqra4Ae80X72g0h2N+jdrA4BgoMQmz9JaQmKAEXUHw9N21DPIBqIyMjOo2fK9ISiGytlAOxBAJf1hEvqg==", + "type": "package", + "path": "microsoft.data.sqlite.core/9.0.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net6.0/Microsoft.Data.Sqlite.dll", + "lib/net6.0/Microsoft.Data.Sqlite.xml", + "lib/net8.0/Microsoft.Data.Sqlite.dll", + "lib/net8.0/Microsoft.Data.Sqlite.xml", + "lib/netstandard2.0/Microsoft.Data.Sqlite.dll", + "lib/netstandard2.0/Microsoft.Data.Sqlite.xml", + "microsoft.data.sqlite.core.9.0.5.nupkg.sha512", + "microsoft.data.sqlite.core.nuspec" + ] + }, + "SQLitePCLRaw.bundle_e_sqlite3/2.1.10": { + "sha512": "UxWuisvZ3uVcVOLJQv7urM/JiQH+v3TmaJc1BLKl5Dxfm/nTzTUrqswCqg/INiYLi61AXnHo1M1JPmPqqLnAdg==", + "type": "package", + "path": "sqlitepclraw.bundle_e_sqlite3/2.1.10", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/monoandroid90/SQLitePCLRaw.batteries_v2.dll", + "lib/net461/SQLitePCLRaw.batteries_v2.dll", + "lib/net6.0-android31.0/SQLitePCLRaw.batteries_v2.dll", + "lib/net6.0-android31.0/SQLitePCLRaw.batteries_v2.xml", + "lib/net6.0-ios14.0/SQLitePCLRaw.batteries_v2.dll", + "lib/net6.0-ios14.2/SQLitePCLRaw.batteries_v2.dll", + "lib/net6.0-tvos10.0/SQLitePCLRaw.batteries_v2.dll", + "lib/netstandard2.0/SQLitePCLRaw.batteries_v2.dll", + "lib/xamarinios10/SQLitePCLRaw.batteries_v2.dll", + "sqlitepclraw.bundle_e_sqlite3.2.1.10.nupkg.sha512", + "sqlitepclraw.bundle_e_sqlite3.nuspec" + ] + }, + "SQLitePCLRaw.core/2.1.10": { + "sha512": "Ii8JCbC7oiVclaE/mbDEK000EFIJ+ShRPwAvvV89GOZhQ+ZLtlnSWl6ksCNMKu/VGXA4Nfi2B7LhN/QFN9oBcw==", + "type": "package", + "path": "sqlitepclraw.core/2.1.10", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/netstandard2.0/SQLitePCLRaw.core.dll", + "sqlitepclraw.core.2.1.10.nupkg.sha512", + "sqlitepclraw.core.nuspec" + ] + }, + "SQLitePCLRaw.lib.e_sqlite3/2.1.10": { + "sha512": "mAr69tDbnf3QJpRy2nJz8Qdpebdil00fvycyByR58Cn9eARvR+UiG2Vzsp+4q1tV3ikwiYIjlXCQFc12GfebbA==", + "type": "package", + "path": "sqlitepclraw.lib.e_sqlite3/2.1.10", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "buildTransitive/net461/SQLitePCLRaw.lib.e_sqlite3.targets", + "buildTransitive/net6.0/SQLitePCLRaw.lib.e_sqlite3.targets", + "buildTransitive/net7.0/SQLitePCLRaw.lib.e_sqlite3.targets", + "buildTransitive/net8.0/SQLitePCLRaw.lib.e_sqlite3.targets", + "buildTransitive/net9.0/SQLitePCLRaw.lib.e_sqlite3.targets", + "lib/net461/_._", + "lib/netstandard2.0/_._", + "runtimes/browser-wasm/nativeassets/net6.0/e_sqlite3.a", + "runtimes/browser-wasm/nativeassets/net7.0/e_sqlite3.a", + "runtimes/browser-wasm/nativeassets/net8.0/e_sqlite3.a", + "runtimes/browser-wasm/nativeassets/net9.0/e_sqlite3.a", + "runtimes/linux-arm/native/libe_sqlite3.so", + "runtimes/linux-arm64/native/libe_sqlite3.so", + "runtimes/linux-armel/native/libe_sqlite3.so", + "runtimes/linux-mips64/native/libe_sqlite3.so", + "runtimes/linux-musl-arm/native/libe_sqlite3.so", + "runtimes/linux-musl-arm64/native/libe_sqlite3.so", + "runtimes/linux-musl-s390x/native/libe_sqlite3.so", + "runtimes/linux-musl-x64/native/libe_sqlite3.so", + "runtimes/linux-ppc64le/native/libe_sqlite3.so", + "runtimes/linux-s390x/native/libe_sqlite3.so", + "runtimes/linux-x64/native/libe_sqlite3.so", + "runtimes/linux-x86/native/libe_sqlite3.so", + "runtimes/maccatalyst-arm64/native/libe_sqlite3.dylib", + "runtimes/maccatalyst-x64/native/libe_sqlite3.dylib", + "runtimes/osx-arm64/native/libe_sqlite3.dylib", + "runtimes/osx-x64/native/libe_sqlite3.dylib", + "runtimes/win-arm/native/e_sqlite3.dll", + "runtimes/win-arm64/native/e_sqlite3.dll", + "runtimes/win-x64/native/e_sqlite3.dll", + "runtimes/win-x86/native/e_sqlite3.dll", + "runtimes/win10-arm/nativeassets/uap10.0/e_sqlite3.dll", + "runtimes/win10-arm64/nativeassets/uap10.0/e_sqlite3.dll", + "runtimes/win10-x64/nativeassets/uap10.0/e_sqlite3.dll", + "runtimes/win10-x86/nativeassets/uap10.0/e_sqlite3.dll", + "sqlitepclraw.lib.e_sqlite3.2.1.10.nupkg.sha512", + "sqlitepclraw.lib.e_sqlite3.nuspec" + ] + }, + "SQLitePCLRaw.provider.e_sqlite3/2.1.10": { + "sha512": "uZVTi02C1SxqzgT0HqTWatIbWGb40iIkfc3FpFCpE/r7g6K0PqzDUeefL6P6HPhDtc6BacN3yQysfzP7ks+wSQ==", + "type": "package", + "path": "sqlitepclraw.provider.e_sqlite3/2.1.10", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net6.0-windows7.0/SQLitePCLRaw.provider.e_sqlite3.dll", + "lib/net6.0/SQLitePCLRaw.provider.e_sqlite3.dll", + "lib/netstandard2.0/SQLitePCLRaw.provider.e_sqlite3.dll", + "sqlitepclraw.provider.e_sqlite3.2.1.10.nupkg.sha512", + "sqlitepclraw.provider.e_sqlite3.nuspec" + ] + }, + "System.CodeDom/9.0.5": { + "sha512": "cuzLM2MWutf9ZBEMPYYfd0DXwYdvntp7VCT6a/wvbKCa2ZuvGmW74xi+YBa2mrfEieAXqM4TNKlMmSnfAfpUoQ==", + "type": "package", + "path": "system.codedom/9.0.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.CodeDom.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/System.CodeDom.targets", + "lib/net462/System.CodeDom.dll", + "lib/net462/System.CodeDom.xml", + "lib/net8.0/System.CodeDom.dll", + "lib/net8.0/System.CodeDom.xml", + "lib/net9.0/System.CodeDom.dll", + "lib/net9.0/System.CodeDom.xml", + "lib/netstandard2.0/System.CodeDom.dll", + "lib/netstandard2.0/System.CodeDom.xml", + "system.codedom.9.0.5.nupkg.sha512", + "system.codedom.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.IO.Packaging/8.0.1": { + "sha512": "KYkIOAvPexQOLDxPO2g0BVoWInnQhPpkFzRqvNrNrMhVT6kqhVr0zEb6KCHlptLFukxnZrjuMVAnxK7pOGUYrw==", + "type": "package", + "path": "system.io.packaging/8.0.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.IO.Packaging.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/System.IO.Packaging.targets", + "lib/net462/System.IO.Packaging.dll", + "lib/net462/System.IO.Packaging.xml", + "lib/net6.0/System.IO.Packaging.dll", + "lib/net6.0/System.IO.Packaging.xml", + "lib/net7.0/System.IO.Packaging.dll", + "lib/net7.0/System.IO.Packaging.xml", + "lib/net8.0/System.IO.Packaging.dll", + "lib/net8.0/System.IO.Packaging.xml", + "lib/netstandard2.0/System.IO.Packaging.dll", + "lib/netstandard2.0/System.IO.Packaging.xml", + "system.io.packaging.8.0.1.nupkg.sha512", + "system.io.packaging.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Management/9.0.5": { + "sha512": "n6o9PZm9p25+zAzC3/48K0oHnaPKTInRrxqFq1fi/5TPbMLjuoCm/h//mS3cUmSy+9AO1Z+qsC/Ilt/ZFatv5Q==", + "type": "package", + "path": "system.management/9.0.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/System.Management.targets", + "lib/net462/_._", + "lib/net8.0/System.Management.dll", + "lib/net8.0/System.Management.xml", + "lib/net9.0/System.Management.dll", + "lib/net9.0/System.Management.xml", + "lib/netstandard2.0/System.Management.dll", + "lib/netstandard2.0/System.Management.xml", + "runtimes/win/lib/net8.0/System.Management.dll", + "runtimes/win/lib/net8.0/System.Management.xml", + "runtimes/win/lib/net9.0/System.Management.dll", + "runtimes/win/lib/net9.0/System.Management.xml", + "system.management.9.0.5.nupkg.sha512", + "system.management.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Memory/4.5.3": { + "sha512": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA==", + "type": "package", + "path": "system.memory/4.5.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/netcoreapp2.1/_._", + "lib/netstandard1.1/System.Memory.dll", + "lib/netstandard1.1/System.Memory.xml", + "lib/netstandard2.0/System.Memory.dll", + "lib/netstandard2.0/System.Memory.xml", + "ref/netcoreapp2.1/_._", + "system.memory.4.5.3.nupkg.sha512", + "system.memory.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "Kit.Core.Helpers/1.0.0": { + "type": "project", + "path": "../Kit.Core.Helpers/Kit.Core.Helpers.csproj", + "msbuildProject": "../Kit.Core.Helpers/Kit.Core.Helpers.csproj" + }, + "Npgsql/1.0.0": { + "type": "project", + "path": "../../LibExternal/Npgsql/Npgsql.csproj", + "msbuildProject": "../../LibExternal/Npgsql/Npgsql.csproj" + } + }, + "projectFileDependencyGroups": { + "net8.0": [ + "DocumentFormat.OpenXml >= 3.3.0", + "Kit.Core.Helpers >= 1.0.0" + ] + }, + "packageFolders": { + "C:\\Users\\user\\.nuget\\packages\\": {}, + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Helpers.OpenXml\\Kit.Helpers.OpenXml.csproj", + "projectName": "Kit.Helpers.OpenXml", + "projectPath": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Helpers.OpenXml\\Kit.Helpers.OpenXml.csproj", + "packagesPath": "C:\\Users\\user\\.nuget\\packages\\", + "outputPath": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Helpers.OpenXml\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\user\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "C:\\GIT\\RiskProf.Modules.Core\\RiskProf.LK.Back\\RiskProf.LK.Back\\packages": {}, + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": { + "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj": { + "projectPath": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "dependencies": { + "DocumentFormat.OpenXml": { + "target": "Package", + "version": "[3.3.0, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.302/PortableRuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Helpers.OpenXml/obj/project.nuget.cache b/LibCommon/Kit.Helpers.OpenXml/obj/project.nuget.cache new file mode 100644 index 0000000..cc81255 --- /dev/null +++ b/LibCommon/Kit.Helpers.OpenXml/obj/project.nuget.cache @@ -0,0 +1,21 @@ +{ + "version": 2, + "dgSpecHash": "aKOljPaTJ64=", + "success": true, + "projectFilePath": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Helpers.OpenXml\\Kit.Helpers.OpenXml.csproj", + "expectedPackageFiles": [ + "C:\\Users\\user\\.nuget\\packages\\documentformat.openxml\\3.3.0\\documentformat.openxml.3.3.0.nupkg.sha512", + "C:\\Users\\user\\.nuget\\packages\\documentformat.openxml.framework\\3.3.0\\documentformat.openxml.framework.3.3.0.nupkg.sha512", + "C:\\Users\\user\\.nuget\\packages\\microsoft.data.sqlite\\9.0.5\\microsoft.data.sqlite.9.0.5.nupkg.sha512", + "C:\\Users\\user\\.nuget\\packages\\microsoft.data.sqlite.core\\9.0.5\\microsoft.data.sqlite.core.9.0.5.nupkg.sha512", + "C:\\Users\\user\\.nuget\\packages\\sqlitepclraw.bundle_e_sqlite3\\2.1.10\\sqlitepclraw.bundle_e_sqlite3.2.1.10.nupkg.sha512", + "C:\\Users\\user\\.nuget\\packages\\sqlitepclraw.core\\2.1.10\\sqlitepclraw.core.2.1.10.nupkg.sha512", + "C:\\Users\\user\\.nuget\\packages\\sqlitepclraw.lib.e_sqlite3\\2.1.10\\sqlitepclraw.lib.e_sqlite3.2.1.10.nupkg.sha512", + "C:\\Users\\user\\.nuget\\packages\\sqlitepclraw.provider.e_sqlite3\\2.1.10\\sqlitepclraw.provider.e_sqlite3.2.1.10.nupkg.sha512", + "C:\\Users\\user\\.nuget\\packages\\system.codedom\\9.0.5\\system.codedom.9.0.5.nupkg.sha512", + "C:\\Users\\user\\.nuget\\packages\\system.io.packaging\\8.0.1\\system.io.packaging.8.0.1.nupkg.sha512", + "C:\\Users\\user\\.nuget\\packages\\system.management\\9.0.5\\system.management.9.0.5.nupkg.sha512", + "C:\\Users\\user\\.nuget\\packages\\system.memory\\4.5.3\\system.memory.4.5.3.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/LibCommon/Kit.Sign/Kit.Sign.csproj b/LibCommon/Kit.Sign/Kit.Sign.csproj new file mode 100644 index 0000000..b5531ed --- /dev/null +++ b/LibCommon/Kit.Sign/Kit.Sign.csproj @@ -0,0 +1,10 @@ + + + ..\..\Build\$(Configuration)\$(TargetFramework) + net8.0 + enable + enable + fc9d4bda-3c73-4d6e-8cc9-3df23a92e818 + + + diff --git a/LibCommon/Kit.Sign/Services/SignService.cs b/LibCommon/Kit.Sign/Services/SignService.cs new file mode 100644 index 0000000..60169a8 --- /dev/null +++ b/LibCommon/Kit.Sign/Services/SignService.cs @@ -0,0 +1,117 @@ +using System.Reflection; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace Kit.Sign +{ + [Obfuscation(Exclude = true, ApplyToMembers = true, StripAfterObfuscation = true)] + public interface ISignService + { + void Sign(Stream P_0, Stream P_1); + void Verify(Stream P_0, Stream P_1, out bool P_2); + } + + [Obfuscation(Exclude = true, ApplyToMembers = false, StripAfterObfuscation = true)] + public class SignService : ISignService + { + private SignServicePrivate _service; + public SignService(string? privateKeyString, string? publicKeyString, string? password) + { + _service = new SignServicePrivate(privateKeyString, publicKeyString, password); + } + + public void Sign(Stream dataStream, Stream signatureStream) => _service.Sign(dataStream, signatureStream); + public void Verify(Stream dataStream, Stream signatureStream, out bool isValid) => isValid = _service.Verify(dataStream, signatureStream); + } + + internal class SignServicePrivate + { + private string? _privateKeyString; + private string? _publicKeyString; + private string? _password; + private readonly HashAlgorithmName _hashAlgorithmName; + private readonly RSASignaturePadding _signaturePadding; + public SignServicePrivate(string? privateKeyString, string? publicKeyString, string? password) + { + _privateKeyString = privateKeyString; + _publicKeyString = publicKeyString; + _password = password; + _hashAlgorithmName = HashAlgorithmName.SHA256; + _signaturePadding = RSASignaturePadding.Pkcs1; + } + + private ReadOnlySpan GetPrivateKey() => Convert.FromBase64String(_privateKeyString.Split("-", StringSplitOptions.RemoveEmptyEntries)[1]); + private ReadOnlySpan GetPublicKey() => Encoding.UTF8.GetBytes(_publicKeyString); + + private RSA ImportPrivate(RSA rsa) + { + if (_privateKeyString == null || _privateKeyString.Length == 0) throw new InvalidOperationException($"{nameof(ImportPrivate)} is unavailable"); + if (string.IsNullOrWhiteSpace(_password) == false) + rsa.ImportEncryptedPkcs8PrivateKey(_password, GetPrivateKey(), out _); + else + rsa.ImportPkcs8PrivateKey(GetPrivateKey(), out _); + + return rsa; + } + + public void Sign(Stream dataStream, Stream signatureStream) + { + if (_privateKeyString == null || _privateKeyString.Length == 0) throw new InvalidOperationException("Sign is unavailable"); + using (RSA rsa = ImportPrivate(RSA.Create())) + { + WriteSignatureBytes(signatureStream, rsa.SignData(ReadAllBytes(dataStream), _hashAlgorithmName, _signaturePadding)); + } + } + + public bool Verify(Stream dataStream, Stream signatureStream) + { + if (_publicKeyString == null || _publicKeyString.Length == 0) throw new InvalidOperationException("Verify is unavailable"); + using var publicX509 = new X509Certificate2(GetPublicKey()); + using (var rsa = publicX509.GetRSAPublicKey()!) + { + return rsa.VerifyData(ReadAllBytes(dataStream), ReadSignatureBytes(signatureStream), _hashAlgorithmName, _signaturePadding); + } + } + + private void WriteSignatureBytes(Stream signatureStream, byte[] signatureBytes) + { + byte[] base64Signature = Encoding.UTF8.GetBytes(Convert.ToBase64String(signatureBytes)); + signatureStream.Write(base64Signature, 0, base64Signature.Length); + } + + private byte[] ReadSignatureBytes(Stream signatureStream) + { + long position = 0; + if (signatureStream.CanSeek) + { + position = signatureStream.Position; + } + string base64Signature = string.Empty; + + using (TextReader textReader = new StreamReader(signatureStream, leaveOpen: true)) + { + base64Signature = textReader.ReadToEnd(); + } + + if (signatureStream.CanSeek) + { + signatureStream.Position = position; + } + + return Convert.FromBase64String(base64Signature); + } + + private static byte[] ReadAllBytes(Stream instream) + { + if (instream is MemoryStream) + return ((MemoryStream)instream).ToArray(); + + using (var memoryStream = new MemoryStream()) + { + instream.CopyTo(memoryStream); + return memoryStream.ToArray(); + } + } + } +} diff --git a/LibCommon/Kit.Sign/bin/Debug/net8.0/Kit.Sign.deps.json b/LibCommon/Kit.Sign/bin/Debug/net8.0/Kit.Sign.deps.json new file mode 100644 index 0000000..8ccecf5 --- /dev/null +++ b/LibCommon/Kit.Sign/bin/Debug/net8.0/Kit.Sign.deps.json @@ -0,0 +1,23 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v8.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v8.0": { + "Kit.Sign/1.0.0": { + "runtime": { + "Kit.Sign.dll": {} + } + } + } + }, + "libraries": { + "Kit.Sign/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Sign/bin/Debug/net8.0/Kit.Sign.dll b/LibCommon/Kit.Sign/bin/Debug/net8.0/Kit.Sign.dll new file mode 100644 index 0000000..edc8f56 Binary files /dev/null and b/LibCommon/Kit.Sign/bin/Debug/net8.0/Kit.Sign.dll differ diff --git a/LibCommon/Kit.Sign/bin/Debug/net8.0/Kit.Sign.pdb b/LibCommon/Kit.Sign/bin/Debug/net8.0/Kit.Sign.pdb new file mode 100644 index 0000000..14adaa1 Binary files /dev/null and b/LibCommon/Kit.Sign/bin/Debug/net8.0/Kit.Sign.pdb differ diff --git a/LibCommon/Kit.Sign/bin/Debug/net8.0/RiskProf.Sign.deps.json b/LibCommon/Kit.Sign/bin/Debug/net8.0/RiskProf.Sign.deps.json new file mode 100644 index 0000000..8b5061c --- /dev/null +++ b/LibCommon/Kit.Sign/bin/Debug/net8.0/RiskProf.Sign.deps.json @@ -0,0 +1,23 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v8.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v8.0": { + "RiskProf.Sign/1.0.0": { + "runtime": { + "RiskProf.Sign.dll": {} + } + } + } + }, + "libraries": { + "RiskProf.Sign/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Sign/bin/Debug/net8.0/RiskProf.Sign.dll b/LibCommon/Kit.Sign/bin/Debug/net8.0/RiskProf.Sign.dll new file mode 100644 index 0000000..d525791 Binary files /dev/null and b/LibCommon/Kit.Sign/bin/Debug/net8.0/RiskProf.Sign.dll differ diff --git a/LibCommon/Kit.Sign/bin/Debug/net8.0/RiskProf.Sign.pdb b/LibCommon/Kit.Sign/bin/Debug/net8.0/RiskProf.Sign.pdb new file mode 100644 index 0000000..07d21d2 Binary files /dev/null and b/LibCommon/Kit.Sign/bin/Debug/net8.0/RiskProf.Sign.pdb differ diff --git a/LibCommon/Kit.Sign/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs b/LibCommon/Kit.Sign/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs new file mode 100644 index 0000000..2217181 --- /dev/null +++ b/LibCommon/Kit.Sign/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")] diff --git a/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.AssemblyInfo.cs b/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.AssemblyInfo.cs new file mode 100644 index 0000000..9afda41 --- /dev/null +++ b/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.AssemblyInfo.cs @@ -0,0 +1,23 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("Kit.Sign")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+37e709e30d2205dd559831f65211b8dd5d40a303")] +[assembly: System.Reflection.AssemblyProductAttribute("Kit.Sign")] +[assembly: System.Reflection.AssemblyTitleAttribute("Kit.Sign")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.AssemblyInfoInputs.cache b/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.AssemblyInfoInputs.cache new file mode 100644 index 0000000..d8c3968 --- /dev/null +++ b/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +7686451df9685c04d53d7baeb94e4298484809b356dfde7bc0a80b8e1184ff28 diff --git a/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.GeneratedMSBuildEditorConfig.editorconfig b/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..1f810f9 --- /dev/null +++ b/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,15 @@ +is_global = true +build_property.TargetFramework = net8.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = Kit.Sign +build_property.ProjectDir = C:\KIT\Kit.Core\LibCommon\Kit.Sign\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 8.0 +build_property.EnableCodeStyleSeverity = diff --git a/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.GlobalUsings.g.cs b/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.GlobalUsings.g.cs new file mode 100644 index 0000000..8578f3d --- /dev/null +++ b/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.GlobalUsings.g.cs @@ -0,0 +1,8 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.assets.cache b/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.assets.cache similarity index 56% rename from Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.assets.cache rename to LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.assets.cache index aa2e0b3..1bed432 100644 Binary files a/Kit.Core.Helpers/obj/Debug/net8.0/Kit.Core.Helpers.assets.cache and b/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.assets.cache differ diff --git a/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.csproj.CoreCompileInputs.cache b/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..859eb35 --- /dev/null +++ b/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +be5df95204c2be06a60c4cb9fecf92e2f94c67dac47c5742f75c1da34a9ba41e diff --git a/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.csproj.FileListAbsolute.txt b/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..d58b301 --- /dev/null +++ b/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.csproj.FileListAbsolute.txt @@ -0,0 +1,11 @@ +C:\KIT\Kit.Core\LibCommon\Kit.Sign\bin\Debug\net8.0\Kit.Sign.deps.json +C:\KIT\Kit.Core\LibCommon\Kit.Sign\bin\Debug\net8.0\Kit.Sign.dll +C:\KIT\Kit.Core\LibCommon\Kit.Sign\bin\Debug\net8.0\Kit.Sign.pdb +C:\KIT\Kit.Core\LibCommon\Kit.Sign\obj\Debug\net8.0\Kit.Sign.GeneratedMSBuildEditorConfig.editorconfig +C:\KIT\Kit.Core\LibCommon\Kit.Sign\obj\Debug\net8.0\Kit.Sign.AssemblyInfoInputs.cache +C:\KIT\Kit.Core\LibCommon\Kit.Sign\obj\Debug\net8.0\Kit.Sign.AssemblyInfo.cs +C:\KIT\Kit.Core\LibCommon\Kit.Sign\obj\Debug\net8.0\Kit.Sign.csproj.CoreCompileInputs.cache +C:\KIT\Kit.Core\LibCommon\Kit.Sign\obj\Debug\net8.0\Kit.Sign.dll +C:\KIT\Kit.Core\LibCommon\Kit.Sign\obj\Debug\net8.0\refint\Kit.Sign.dll +C:\KIT\Kit.Core\LibCommon\Kit.Sign\obj\Debug\net8.0\Kit.Sign.pdb +C:\KIT\Kit.Core\LibCommon\Kit.Sign\obj\Debug\net8.0\ref\Kit.Sign.dll diff --git a/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.dll b/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.dll new file mode 100644 index 0000000..edc8f56 Binary files /dev/null and b/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.dll differ diff --git a/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.pdb b/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.pdb new file mode 100644 index 0000000..14adaa1 Binary files /dev/null and b/LibCommon/Kit.Sign/obj/Debug/net8.0/Kit.Sign.pdb differ diff --git a/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.AssemblyInfo.cs b/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.AssemblyInfo.cs new file mode 100644 index 0000000..77083fc --- /dev/null +++ b/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.AssemblyInfo.cs @@ -0,0 +1,23 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("RiskProf.Sign")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+37e709e30d2205dd559831f65211b8dd5d40a303")] +[assembly: System.Reflection.AssemblyProductAttribute("RiskProf.Sign")] +[assembly: System.Reflection.AssemblyTitleAttribute("RiskProf.Sign")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.AssemblyInfoInputs.cache b/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.AssemblyInfoInputs.cache new file mode 100644 index 0000000..1349239 --- /dev/null +++ b/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +8a9d9f01cd994f2da837257b033b1c706aa0f7928b5558cf01a9bf70e63d2102 diff --git a/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.GeneratedMSBuildEditorConfig.editorconfig b/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..c0edac6 --- /dev/null +++ b/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,15 @@ +is_global = true +build_property.TargetFramework = net8.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = RiskProf.Sign +build_property.ProjectDir = C:\KIT\Kit.Core\LibCommon\RiskProf.Sign\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 8.0 +build_property.EnableCodeStyleSeverity = diff --git a/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.GlobalUsings.g.cs b/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.GlobalUsings.g.cs new file mode 100644 index 0000000..8578f3d --- /dev/null +++ b/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.GlobalUsings.g.cs @@ -0,0 +1,8 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.assets.cache b/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.assets.cache new file mode 100644 index 0000000..b770335 Binary files /dev/null and b/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.assets.cache differ diff --git a/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.csproj.CoreCompileInputs.cache b/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..1590386 --- /dev/null +++ b/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +e166a589496709bf70bc8ac71dd8c92fad68d49b6a265b3bc7162ff9f2625829 diff --git a/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.csproj.FileListAbsolute.txt b/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..4686d7e --- /dev/null +++ b/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.csproj.FileListAbsolute.txt @@ -0,0 +1,11 @@ +C:\KIT\Kit.Core\LibCommon\RiskProf.Sign\bin\Debug\net8.0\RiskProf.Sign.deps.json +C:\KIT\Kit.Core\LibCommon\RiskProf.Sign\bin\Debug\net8.0\RiskProf.Sign.dll +C:\KIT\Kit.Core\LibCommon\RiskProf.Sign\bin\Debug\net8.0\RiskProf.Sign.pdb +C:\KIT\Kit.Core\LibCommon\RiskProf.Sign\obj\Debug\net8.0\RiskProf.Sign.GeneratedMSBuildEditorConfig.editorconfig +C:\KIT\Kit.Core\LibCommon\RiskProf.Sign\obj\Debug\net8.0\RiskProf.Sign.AssemblyInfoInputs.cache +C:\KIT\Kit.Core\LibCommon\RiskProf.Sign\obj\Debug\net8.0\RiskProf.Sign.AssemblyInfo.cs +C:\KIT\Kit.Core\LibCommon\RiskProf.Sign\obj\Debug\net8.0\RiskProf.Sign.csproj.CoreCompileInputs.cache +C:\KIT\Kit.Core\LibCommon\RiskProf.Sign\obj\Debug\net8.0\RiskProf.Sign.dll +C:\KIT\Kit.Core\LibCommon\RiskProf.Sign\obj\Debug\net8.0\refint\RiskProf.Sign.dll +C:\KIT\Kit.Core\LibCommon\RiskProf.Sign\obj\Debug\net8.0\RiskProf.Sign.pdb +C:\KIT\Kit.Core\LibCommon\RiskProf.Sign\obj\Debug\net8.0\ref\RiskProf.Sign.dll diff --git a/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.dll b/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.dll new file mode 100644 index 0000000..d525791 Binary files /dev/null and b/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.dll differ diff --git a/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.pdb b/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.pdb new file mode 100644 index 0000000..07d21d2 Binary files /dev/null and b/LibCommon/Kit.Sign/obj/Debug/net8.0/RiskProf.Sign.pdb differ diff --git a/LibCommon/Kit.Sign/obj/Debug/net8.0/ref/Kit.Sign.dll b/LibCommon/Kit.Sign/obj/Debug/net8.0/ref/Kit.Sign.dll new file mode 100644 index 0000000..a17e0ca Binary files /dev/null and b/LibCommon/Kit.Sign/obj/Debug/net8.0/ref/Kit.Sign.dll differ diff --git a/LibCommon/Kit.Sign/obj/Debug/net8.0/ref/RiskProf.Sign.dll b/LibCommon/Kit.Sign/obj/Debug/net8.0/ref/RiskProf.Sign.dll new file mode 100644 index 0000000..4c2f5cb Binary files /dev/null and b/LibCommon/Kit.Sign/obj/Debug/net8.0/ref/RiskProf.Sign.dll differ diff --git a/LibCommon/Kit.Sign/obj/Debug/net8.0/refint/Kit.Sign.dll b/LibCommon/Kit.Sign/obj/Debug/net8.0/refint/Kit.Sign.dll new file mode 100644 index 0000000..a17e0ca Binary files /dev/null and b/LibCommon/Kit.Sign/obj/Debug/net8.0/refint/Kit.Sign.dll differ diff --git a/LibCommon/Kit.Sign/obj/Debug/net8.0/refint/RiskProf.Sign.dll b/LibCommon/Kit.Sign/obj/Debug/net8.0/refint/RiskProf.Sign.dll new file mode 100644 index 0000000..4c2f5cb Binary files /dev/null and b/LibCommon/Kit.Sign/obj/Debug/net8.0/refint/RiskProf.Sign.dll differ diff --git a/LibCommon/Kit.Sign/obj/Kit.Sign.csproj.nuget.dgspec.json b/LibCommon/Kit.Sign/obj/Kit.Sign.csproj.nuget.dgspec.json new file mode 100644 index 0000000..d135912 --- /dev/null +++ b/LibCommon/Kit.Sign/obj/Kit.Sign.csproj.nuget.dgspec.json @@ -0,0 +1,74 @@ +{ + "format": 1, + "restore": { + "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Sign\\Kit.Sign.csproj": {} + }, + "projects": { + "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Sign\\Kit.Sign.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Sign\\Kit.Sign.csproj", + "projectName": "Kit.Sign", + "projectPath": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Sign\\Kit.Sign.csproj", + "packagesPath": "C:\\Users\\user\\.nuget\\packages\\", + "outputPath": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Sign\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\user\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "C:\\GIT\\RiskProf.Modules.Core\\RiskProf.LK.Back\\RiskProf.LK.Back\\packages": {}, + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.302/PortableRuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Sign/obj/Kit.Sign.csproj.nuget.g.props b/LibCommon/Kit.Sign/obj/Kit.Sign.csproj.nuget.g.props new file mode 100644 index 0000000..020441f --- /dev/null +++ b/LibCommon/Kit.Sign/obj/Kit.Sign.csproj.nuget.g.props @@ -0,0 +1,16 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\user\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages + PackageReference + 6.14.0 + + + + + + \ No newline at end of file diff --git a/Kit.Core.Helpers/obj/Kit.Core.Helpers.csproj.nuget.g.targets b/LibCommon/Kit.Sign/obj/Kit.Sign.csproj.nuget.g.targets similarity index 100% rename from Kit.Core.Helpers/obj/Kit.Core.Helpers.csproj.nuget.g.targets rename to LibCommon/Kit.Sign/obj/Kit.Sign.csproj.nuget.g.targets diff --git a/LibCommon/Kit.Sign/obj/RiskProf.Sign.csproj.nuget.dgspec.json b/LibCommon/Kit.Sign/obj/RiskProf.Sign.csproj.nuget.dgspec.json new file mode 100644 index 0000000..b6631d6 --- /dev/null +++ b/LibCommon/Kit.Sign/obj/RiskProf.Sign.csproj.nuget.dgspec.json @@ -0,0 +1,74 @@ +{ + "format": 1, + "restore": { + "C:\\KIT\\Kit.Core\\LibCommon\\RiskProf.Sign\\RiskProf.Sign.csproj": {} + }, + "projects": { + "C:\\KIT\\Kit.Core\\LibCommon\\RiskProf.Sign\\RiskProf.Sign.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\KIT\\Kit.Core\\LibCommon\\RiskProf.Sign\\RiskProf.Sign.csproj", + "projectName": "RiskProf.Sign", + "projectPath": "C:\\KIT\\Kit.Core\\LibCommon\\RiskProf.Sign\\RiskProf.Sign.csproj", + "packagesPath": "C:\\Users\\user\\.nuget\\packages\\", + "outputPath": "C:\\KIT\\Kit.Core\\LibCommon\\RiskProf.Sign\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\user\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "C:\\GIT\\RiskProf.Modules.Core\\RiskProf.LK.Back\\RiskProf.LK.Back\\packages": {}, + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.302/PortableRuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Sign/obj/RiskProf.Sign.csproj.nuget.g.props b/LibCommon/Kit.Sign/obj/RiskProf.Sign.csproj.nuget.g.props new file mode 100644 index 0000000..020441f --- /dev/null +++ b/LibCommon/Kit.Sign/obj/RiskProf.Sign.csproj.nuget.g.props @@ -0,0 +1,16 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\user\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages + PackageReference + 6.14.0 + + + + + + \ No newline at end of file diff --git a/LibCommon/Kit.Sign/obj/RiskProf.Sign.csproj.nuget.g.targets b/LibCommon/Kit.Sign/obj/RiskProf.Sign.csproj.nuget.g.targets new file mode 100644 index 0000000..3dc06ef --- /dev/null +++ b/LibCommon/Kit.Sign/obj/RiskProf.Sign.csproj.nuget.g.targets @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/LibCommon/Kit.Sign/obj/project.assets.json b/LibCommon/Kit.Sign/obj/project.assets.json new file mode 100644 index 0000000..03f3abd --- /dev/null +++ b/LibCommon/Kit.Sign/obj/project.assets.json @@ -0,0 +1,80 @@ +{ + "version": 3, + "targets": { + "net8.0": {} + }, + "libraries": {}, + "projectFileDependencyGroups": { + "net8.0": [] + }, + "packageFolders": { + "C:\\Users\\user\\.nuget\\packages\\": {}, + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Sign\\Kit.Sign.csproj", + "projectName": "Kit.Sign", + "projectPath": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Sign\\Kit.Sign.csproj", + "packagesPath": "C:\\Users\\user\\.nuget\\packages\\", + "outputPath": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Sign\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\user\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "C:\\GIT\\RiskProf.Modules.Core\\RiskProf.LK.Back\\RiskProf.LK.Back\\packages": {}, + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.302/PortableRuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/LibCommon/Kit.Sign/obj/project.nuget.cache b/LibCommon/Kit.Sign/obj/project.nuget.cache new file mode 100644 index 0000000..7674258 --- /dev/null +++ b/LibCommon/Kit.Sign/obj/project.nuget.cache @@ -0,0 +1,8 @@ +{ + "version": 2, + "dgSpecHash": "58V7m4gbKHw=", + "success": true, + "projectFilePath": "C:\\KIT\\Kit.Core\\LibCommon\\Kit.Sign\\Kit.Sign.csproj", + "expectedPackageFiles": [], + "logs": [] +} \ No newline at end of file diff --git a/LibExternal/Minio/AWSS3Endpoints.cs b/LibExternal/Minio/AWSS3Endpoints.cs new file mode 100644 index 0000000..40cc2c5 --- /dev/null +++ b/LibExternal/Minio/AWSS3Endpoints.cs @@ -0,0 +1,79 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Collections.Concurrent; + +namespace Minio; + +/// +/// Amazon AWS S3 endpoints for various regions. +/// +public sealed class AWSS3Endpoints +{ + private static readonly Lazy lazy = new(() => new AWSS3Endpoints()); + + private readonly ConcurrentDictionary endpoints; + + private AWSS3Endpoints() + { + endpoints = new ConcurrentDictionary(StringComparer.Ordinal); + // ap-northeast-1 + _ = endpoints.TryAdd("ap-northeast-1", "s3-ap-northeast-1.amazonaws.com"); + // ap-northeast-2 + _ = endpoints.TryAdd("ap-northeast-2", "s3-ap-northeast-2.amazonaws.com"); + // ap-south-1 + _ = endpoints.TryAdd("ap-south-1", "s3-ap-south-1.amazonaws.com"); + // ap-southeast-1 + _ = endpoints.TryAdd("ap-southeast-1", "s3-ap-southeast-1.amazonaws.com"); + // ap-southeast-2 + _ = endpoints.TryAdd("ap-southeast-2", "s3-ap-southeast-2.amazonaws.com"); + // eu-central-1 + _ = endpoints.TryAdd("eu-central-1", "s3-eu-central-1.amazonaws.com"); + // eu-west-1 + _ = endpoints.TryAdd("eu-west-1", "s3-eu-west-1.amazonaws.com"); + // eu-west-2 + _ = endpoints.TryAdd("eu-west-2", "s3-eu-west-2.amazonaws.com"); + // sa-east-1 + _ = endpoints.TryAdd("sa-east-1", "s3-sa-east-1.amazonaws.com"); + // us-west-1 + _ = endpoints.TryAdd("us-west-1", "s3-us-west-1.amazonaws.com"); + // us-west-2 + _ = endpoints.TryAdd("us-west-2", "s3-us-west-2.amazonaws.com"); + // us-east-1 + _ = endpoints.TryAdd("us-east-1", "s3.amazonaws.com"); + // us-east-2 + _ = endpoints.TryAdd("us-east-2", "s3-us-east-2.amazonaws.com"); + // ca-central-1 + _ = endpoints.TryAdd("ca-central-1", "s3.ca-central-1.amazonaws.com"); + // cn-north-1 + _ = endpoints.TryAdd("cn-north-1", "s3.cn-north-1.amazonaws.com.cn"); + } + + public static AWSS3Endpoints Instance => lazy.Value; + + /// + /// Gets Amazon S3 endpoint for the relevant region. + /// + /// + /// + public static string Endpoint(string region) + { + string endpoint = null; + if (region is not null) _ = Instance.endpoints.TryGetValue(region, out endpoint); + endpoint ??= "s3.amazonaws.com"; + return endpoint; + } +} diff --git a/LibExternal/Minio/ApiEndpoints/BucketOperations.cs b/LibExternal/Minio/ApiEndpoints/BucketOperations.cs new file mode 100644 index 0000000..9c70224 --- /dev/null +++ b/LibExternal/Minio/ApiEndpoints/BucketOperations.cs @@ -0,0 +1,875 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2017-2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Diagnostics.CodeAnalysis; +using System.Net; +using System.Reactive.Linq; +using Minio.ApiEndpoints; +using Minio.DataModel; +using Minio.DataModel.Args; +using Minio.DataModel.Encryption; +using Minio.DataModel.ILM; +using Minio.DataModel.Notification; +using Minio.DataModel.ObjectLock; +using Minio.DataModel.Replication; +using Minio.DataModel.Response; +using Minio.DataModel.Result; +using Minio.DataModel.Tags; +using Minio.Exceptions; +using Minio.Helper; + +namespace Minio; + +[SuppressMessage("Design", "MA0048:File name must match type name", Justification = "Split up in partial classes")] +public partial class MinioClient : IBucketOperations +{ + /// + /// List all the buckets for the current Endpoint URL + /// + /// Optional cancellation token to cancel the operation + /// Task with an iterator lazily populated with objects + public async Task ListBucketsAsync( + CancellationToken cancellationToken = default) + { + var requestMessageBuilder = await this.CreateRequest(HttpMethod.Get).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + + var bucketList = new ListAllMyBucketsResult(); + if (HttpStatusCode.OK.Equals(response.StatusCode)) + { + using var stream = new MemoryStream(response.ContentBytes.ToArray()); + bucketList = Utils.DeserializeXml(stream); + } + + return bucketList; + } + + /// + /// Check if a private bucket with the given name exists. + /// + /// BucketExistsArgs Arguments Object which has bucket identifier information - bucket name, region + /// Optional cancellation token to cancel the operation + /// Task + public async Task BucketExistsAsync(BucketExistsArgs args, CancellationToken cancellationToken = default) + { + args?.Validate(); + try + { + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken).ConfigureAwait(false); + if (response.Exception is not null && + response.Exception.GetType() == typeof(BucketNotFoundException)) + return false; + } + catch (InternalClientException ice) + { + if ((ice.ServerResponse is not null && + HttpStatusCode.NotFound.Equals(ice.ServerResponse.StatusCode)) || + ice.ServerResponse is null) + return false; + } + catch (Exception ex) + { + if (ex.GetType() == typeof(BucketNotFoundException)) return false; + throw; + } + + return true; + } + + /// + /// Remove the bucket with the given name. + /// + /// RemoveBucketArgs Arguments Object which has bucket identifier information like bucket name .etc. + /// Optional cancellation token to cancel the operation + /// Task + /// When bucketName is invalid + /// When bucketName is not found + /// When bucketName is null + public async Task RemoveBucketAsync(RemoveBucketArgs args, CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = await this.ExecuteTaskAsync(ResponseErrorHandlers, + requestMessageBuilder, cancellationToken: cancellationToken).ConfigureAwait(false); + } + + /// + /// Create a bucket with the given name. + /// + /// MakeBucketArgs Arguments Object that has bucket info like name, location. etc + /// Optional cancellation token to cancel the operation + /// Task + /// When access or secret key is invalid + /// When bucketName is invalid + /// When object-lock or another extension is not implemented + public async Task MakeBucketAsync(MakeBucketArgs args, CancellationToken cancellationToken = default) + { + args?.Validate(); + if (string.IsNullOrEmpty(args.Location)) + args.Location = Config.Region; + + if (string.Equals(args.Location, "us-east-1", StringComparison.OrdinalIgnoreCase) && + !string.IsNullOrEmpty(Config.Region)) + args.Location = Config.Region; + + args.IsBucketCreationRequest = true; + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Get Versioning information on the bucket with given bucket name + /// + /// GetVersioningArgs takes bucket as argument. + /// Optional cancellation token to cancel the operation + /// GetVersioningResponse with information populated from REST response + /// When bucketName is invalid + /// When access or secret key is invalid + /// When bucket name is invalid + /// When a functionality or extension is not implemented + /// When bucket is not found + public async Task GetVersioningAsync(GetVersioningArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var responseResult = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + + var versioningResponse = new GetVersioningResponse(responseResult.StatusCode, responseResult.Content); + return versioningResponse.VersioningConfig; + } + + /// + /// Set Versioning as specified on the bucket with given bucket name + /// + /// SetVersioningArgs Arguments Object with information like Bucket name, Versioning configuration + /// Optional cancellation token to cancel the operation + /// Task + /// When access or secret key is invalid + /// When bucket name is invalid + /// When a functionality or extension is not implemented + /// When bucket is not found + /// When configuration XML provided is invalid + public async Task SetVersioningAsync(SetVersioningArgs args, CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = await this.ExecuteTaskAsync(ResponseErrorHandlers, + requestMessageBuilder, cancellationToken: cancellationToken).ConfigureAwait(false); + } + + /// + /// List all objects along with versions non-recursively in a bucket with a given prefix, optionally emulating a + /// directory + /// + /// + /// ListObjectsArgs Arguments Object with information like Bucket name, prefix, recursive listing, + /// versioning + /// + /// Optional cancellation token to cancel the operation + /// An observable of items that client can subscribe to + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// If a functionality or extension (like versioning) is not implemented + /// + /// For example, if you call ListObjectsAsync on a bucket with versioning + /// enabled or object lock enabled + /// + public IObservable ListObjectsAsync(ListObjectsArgs args, CancellationToken cancellationToken = default) + { + args?.Validate(); + return Observable.Create( + async (obs, ct) => + { + var isRunning = true; + var delimiter = args.Recursive ? string.Empty : "/"; + var marker = string.Empty; + uint count = 0; + var versionIdMarker = string.Empty; + var nextContinuationToken = string.Empty; + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, ct); + while (isRunning) + { + var goArgs = new GetObjectListArgs() + .WithBucket(args.BucketName) + .WithPrefix(args.Prefix) + .WithDelimiter(delimiter) + .WithVersions(args.Versions) + .WithContinuationToken(nextContinuationToken) + .WithMarker(marker) + .WithListObjectsV1(!args.UseV2) + .WithHeaders(args.Headers) + .WithVersionIdMarker(versionIdMarker); + if (args.Versions) + { + var objectList = await GetObjectVersionsListAsync(goArgs, cts.Token).ConfigureAwait(false); + var listObjectsItemResponse = new ListObjectVersionResponse(args, objectList, obs); + if (objectList.Item2.Count == 0 && count == 0) return; + + obs = listObjectsItemResponse.ItemObservable; + marker = listObjectsItemResponse.NextKeyMarker; + versionIdMarker = listObjectsItemResponse.NextVerMarker; + isRunning = objectList.Item1.IsTruncated; + } + else + { + var objectList = await GetObjectListAsync(goArgs, cts.Token).ConfigureAwait(false); + if (objectList.Item2.Count == 0 && + objectList.Item1.KeyCount.Equals("0", StringComparison.OrdinalIgnoreCase) && count == 0) + return; + + var listObjectsItemResponse = new ListObjectsItemResponse(args, objectList, obs); + marker = listObjectsItemResponse.NextMarker; + isRunning = objectList.Item1.IsTruncated; + nextContinuationToken = objectList.Item1.IsTruncated + ? objectList.Item1.NextContinuationToken + : string.Empty; + } + + cts.Token.ThrowIfCancellationRequested(); + count++; + } + } + ); + } + + /// + /// Gets notification configuration for this bucket + /// + /// GetBucketNotificationsArgs Arguments Object with information like Bucket name + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + public async Task GetBucketNotificationsAsync(GetBucketNotificationsArgs args, + CancellationToken cancellationToken = default) + { + if (args is null) + throw new ArgumentNullException(nameof(args)); + + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var responseResult = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + var getBucketNotificationsResponse = + new GetBucketNotificationsResponse(responseResult.StatusCode, responseResult.Content); + return getBucketNotificationsResponse.BucketNotificationConfiguration; + } + + /// + /// Sets the notification configuration for this bucket + /// + /// + /// SetBucketNotificationsArgs Arguments Object with information like Bucket name, notification object + /// with configuration to set + /// + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When configuration XML provided is invalid + public async Task SetBucketNotificationsAsync(SetBucketNotificationsArgs args, + CancellationToken cancellationToken = default) + { + if (args is null) + throw new ArgumentNullException(nameof(args)); + + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Removes all bucket notification configurations stored on the server. + /// + /// RemoveAllBucketNotificationsArgs Arguments Object with information like Bucket name + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When configuration XML provided is invalid + public async Task RemoveAllBucketNotificationsAsync(RemoveAllBucketNotificationsArgs args, + CancellationToken cancellationToken = default) + { + if (args is null) + throw new ArgumentNullException(nameof(args)); + + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Subscribes to bucket change notifications (a Minio-only extension) + /// + /// + /// ListenBucketNotificationsArgs Arguments Object with information like Bucket name, listen events, + /// prefix filter keys, suffix filter keys + /// + /// Optional cancellation token to cancel the operation + /// An observable of JSON-based notification events + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When configuration XML provided is invalid + public IObservable ListenBucketNotificationsAsync(ListenBucketNotificationsArgs args, + CancellationToken cancellationToken = default) + { + if (S3utils.IsAmazonEndPoint(Config.BaseUrl)) + // Amazon AWS does not support bucket notifications + throw new ConnectionException( + "Listening for bucket notification is specific only to `minio` server endpoints"); + + return Observable.Create( + async (obs, ct) => + { + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, ct); + var requestMessageBuilder = + await this.CreateRequest(args).ConfigureAwait(false); + args = args.WithNotificationObserver(obs) + .WithEnableTrace(Config.TraceHttp); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + cts.Token.ThrowIfCancellationRequested(); + }); + } + + /// + /// Gets Tagging values set for this bucket + /// + /// GetBucketTagsArgs Arguments Object with information like Bucket name + /// Optional cancellation token to cancel the operation + /// Tagging Object with key-value tag pairs + /// When access or secret key is invalid + /// When bucket name is invalid + /// When a functionality or extension is not implemented + /// When bucket is not found + public async Task GetBucketTagsAsync(GetBucketTagsArgs args, CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var responseResult = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + var getBucketNotificationsResponse = + new GetBucketTagsResponse(responseResult.StatusCode, responseResult.Content); + return getBucketNotificationsResponse.BucketTags; + } + + /// + /// Sets the Encryption Configuration for the mentioned bucket. + /// + /// SetBucketEncryptionArgs Arguments Object with information like Bucket name, encryption config + /// Optional cancellation token to cancel the operation + /// Task + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + public async Task SetBucketEncryptionAsync(SetBucketEncryptionArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var restResponse = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Returns the Encryption Configuration for the mentioned bucket. + /// + /// GetBucketEncryptionArgs Arguments Object encapsulating information like Bucket name + /// Optional cancellation token to cancel the operation + /// An object of type ServerSideEncryptionConfiguration + /// When access or secret key is invalid + /// When a functionality or extension is not implemented + /// When bucket name is invalid + /// When bucket is not found + public async Task GetBucketEncryptionAsync(GetBucketEncryptionArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var responseResult = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + var getBucketEncryptionResponse = + new GetBucketEncryptionResponse(responseResult.StatusCode, responseResult.Content); + return getBucketEncryptionResponse.BucketEncryptionConfiguration; + } + + /// + /// Removes the Encryption Configuration for the mentioned bucket. + /// + /// RemoveBucketEncryptionArgs Arguments Object encapsulating information like Bucket name + /// Optional cancellation token to cancel the operation + /// Task + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + public async Task RemoveBucketEncryptionAsync(RemoveBucketEncryptionArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var restResponse = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Sets the Tagging values for this bucket + /// + /// SetBucketTagsArgs Arguments Object with information like Bucket name, tag key-value pairs + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + public async Task SetBucketTagsAsync(SetBucketTagsArgs args, CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var restResponse = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Removes Tagging values stored for the bucket. + /// + /// RemoveBucketTagsArgs Arguments Object with information like Bucket name + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + public async Task RemoveBucketTagsAsync(RemoveBucketTagsArgs args, CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var restResponse = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Sets the Object Lock Configuration on this bucket + /// + /// + /// SetObjectLockConfigurationArgs Arguments Object with information like Bucket name, object lock + /// configuration to set + /// + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When object lock configuration on bucket is not set + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + public async Task SetObjectLockConfigurationAsync(SetObjectLockConfigurationArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var restResponse = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Gets the Object Lock Configuration on this bucket + /// + /// GetObjectLockConfigurationArgs Arguments Object with information like Bucket name + /// Optional cancellation token to cancel the operation + /// ObjectLockConfiguration object + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When a functionality or extension is not implemented + /// When object lock configuration on bucket is not set + public async Task GetObjectLockConfigurationAsync(GetObjectLockConfigurationArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var responseResult = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + var resp = new GetObjectLockConfigurationResponse(responseResult.StatusCode, responseResult.Content); + return resp.LockConfiguration; + } + + /// + /// Removes the Object Lock Configuration on this bucket + /// + /// RemoveObjectLockConfigurationArgs Arguments Object with information like Bucket name + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When object lock configuration on bucket is not set + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + public async Task RemoveObjectLockConfigurationAsync(RemoveObjectLockConfigurationArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var restResponse = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Sets the Lifecycle configuration for this bucket + /// + /// + /// SetBucketLifecycleArgs Arguments Object with information like Bucket name, Lifecycle configuration + /// object + /// + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + public async Task SetBucketLifecycleAsync(SetBucketLifecycleArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var restResponse = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Gets Lifecycle configuration set for this bucket returned in an object + /// + /// GetBucketLifecycleArgs Arguments Object with information like Bucket name + /// Optional cancellation token to cancel the operation + /// LifecycleConfiguration Object with the lifecycle configuration + /// When access or secret key is invalid + /// When bucket name is invalid + /// When a functionality or extension is not implemented + /// When bucket is not found + public async Task GetBucketLifecycleAsync(GetBucketLifecycleArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var responseResult = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + var response = new GetBucketLifecycleResponse(responseResult.StatusCode, responseResult.Content); + return response.BucketLifecycle; + } + + /// + /// Removes Lifecycle configuration stored for the bucket. + /// + /// RemoveBucketLifecycleArgs Arguments Object with information like Bucket name + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + public async Task RemoveBucketLifecycleAsync(RemoveBucketLifecycleArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var restResponse = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Get Replication configuration for the bucket + /// + /// GetBucketReplicationArgs Arguments Object with information like Bucket name + /// Optional cancellation token to cancel the operation + /// Replication configuration object + /// When access or secret key provided is invalid + /// When bucket name is invalid + /// When bucket replication configuration is not set + /// When a functionality or extension is not implemented + /// When bucket is not found + public async Task GetBucketReplicationAsync(GetBucketReplicationArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var responseResult = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + var response = new GetBucketReplicationResponse(responseResult.StatusCode, responseResult.Content); + return response.Config; + } + + /// + /// Set the Replication configuration for the bucket + /// + /// + /// SetBucketReplicationArgs Arguments Object with information like Bucket name, Replication + /// Configuration object + /// + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key provided is invalid + /// When bucket name is invalid + /// When bucket replication configuration is not set + /// When a functionality or extension is not implemented + /// When bucket is not found + public async Task SetBucketReplicationAsync(SetBucketReplicationArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var restResponse = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Remove Replication configuration for the bucket. + /// + /// RemoveBucketReplicationArgs Arguments Object with information like Bucket name + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key provided is invalid + /// When bucket name is invalid + /// When bucket replication configuration is not set + /// When a functionality or extension is not implemented + /// When bucket is not found + public async Task RemoveBucketReplicationAsync(RemoveBucketReplicationArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var restResponse = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Subscribes to bucket change notifications (a Minio-only extension) + /// + /// Bucket to get notifications from + /// Events to listen for + /// Filter keys starting with this prefix + /// Filter keys ending with this suffix + /// Optional cancellation token to cancel the operation + /// An observable of JSON-based notification events + public IObservable ListenBucketNotificationsAsync( + string bucketName, + IList events, + string prefix = "", + string suffix = "", + CancellationToken cancellationToken = default) + { + var eventList = new List(events); + var args = new ListenBucketNotificationsArgs() + .WithBucket(bucketName) + .WithEvents(eventList) + .WithPrefix(prefix) + .WithSuffix(suffix); + return ListenBucketNotificationsAsync(args, cancellationToken); + } + + /// + /// Returns current policy stored on the server for this bucket + /// + /// GetPolicyArgs object has information like Bucket name. + /// Optional cancellation token to cancel the operation + /// Task that returns the Bucket policy as a json string + /// When bucketName is invalid + /// When a functionality or extension is not implemented + /// When a policy is not set + public async Task GetPolicyAsync(GetPolicyArgs args, CancellationToken cancellationToken = default) + { + if (args is null) + throw new ArgumentNullException(nameof(args)); + + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var responseResult = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + var getPolicyResponse = new GetPolicyResponse(responseResult.StatusCode, responseResult.Content); + return getPolicyResponse.PolicyJsonString; + } + + /// + /// Sets the current bucket policy + /// + /// SetPolicyArgs object has information like Bucket name and the policy to set in Json format + /// Optional cancellation token to cancel the operation + /// When bucketName is invalid + /// When a functionality or extension is not implemented + /// When a policy is not set + /// Task to set a policy + public async Task SetPolicyAsync(SetPolicyArgs args, CancellationToken cancellationToken = default) + { + if (args is null) + throw new ArgumentNullException(nameof(args)); + + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Removes the current bucket policy + /// + /// RemovePolicyArgs object has information like Bucket name + /// Optional cancellation token to cancel the operation + /// Task to set a policy + /// When bucketName is invalid + /// When a functionality or extension is not implemented + /// When a policy is not set + public async Task RemovePolicyAsync(RemovePolicyArgs args, CancellationToken cancellationToken = default) + { + if (args is null) + throw new ArgumentNullException(nameof(args)); + + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Gets the list of objects in the bucket filtered by prefix + /// + /// + /// GetObjectListArgs Arguments Object with information like Bucket name, prefix, delimiter, marker, + /// versions(get version IDs of the objects) + /// + /// Task with a tuple populated with objects + /// Optional cancellation token to cancel the operation + private async Task>> GetObjectListAsync(GetObjectListArgs args, + CancellationToken cancellationToken = default) + { + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var responseResult = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + var getObjectsListResponse = new GetObjectsListResponse(responseResult.StatusCode, responseResult.Content); + return getObjectsListResponse.ObjectsTuple; + } + + /// + /// Gets the list of objects along with version IDs in the bucket filtered by prefix + /// + /// + /// GetObjectListArgs Arguments Object with information like Bucket name, prefix, delimiter, marker, + /// versions(get version IDs of the objects) + /// + /// Task with a tuple populated with objects + /// Optional cancellation token to cancel the operation + private async Task>> GetObjectVersionsListAsync(GetObjectListArgs args, + CancellationToken cancellationToken = default) + { + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var responseResult = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + var getObjectsVersionsListResponse = + new GetObjectsVersionsListResponse(responseResult.StatusCode, responseResult.Content); + return getObjectsVersionsListResponse.ObjectsTuple; + } + + /// + /// Gets the list of objects in the bucket filtered by prefix + /// + /// Bucket to list objects from + /// Filters all objects starting with a given prefix + /// Delimit the output upto this character + /// marks location in the iterator sequence + /// Task with a tuple populated with objects + /// Optional cancellation token to cancel the operation + private Task>> GetObjectListAsync(string bucketName, string prefix, + string delimiter, string marker, CancellationToken cancellationToken = default) + { + // null values are treated as empty strings. + var args = new GetObjectListArgs() + .WithBucket(bucketName) + .WithPrefix(prefix) + .WithDelimiter(delimiter) + .WithMarker(marker); + return GetObjectListAsync(args, cancellationToken); + } +} diff --git a/LibExternal/Minio/ApiEndpoints/IBucketOperations.cs b/LibExternal/Minio/ApiEndpoints/IBucketOperations.cs new file mode 100644 index 0000000..fb3e885 --- /dev/null +++ b/LibExternal/Minio/ApiEndpoints/IBucketOperations.cs @@ -0,0 +1,388 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2017-2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.DataModel; +using Minio.DataModel.Args; +using Minio.DataModel.Encryption; +using Minio.DataModel.ILM; +using Minio.DataModel.Notification; +using Minio.DataModel.ObjectLock; +using Minio.DataModel.Replication; +using Minio.DataModel.Result; +using Minio.DataModel.Tags; +using Minio.Exceptions; + +namespace Minio.ApiEndpoints; + +public interface IBucketOperations +{ + /// + /// Create a bucket with the given name. + /// + /// MakeBucketArgs Arguments Object that has bucket info like name, location. etc + /// Optional cancellation token to cancel the operation + /// Task + /// When access or secret key is invalid + /// When bucketName is invalid + /// When object-lock or another extension is not implemented + Task MakeBucketAsync(MakeBucketArgs args, CancellationToken cancellationToken = default); + + /// + /// List all objects in a bucket + /// + /// Optional cancellation token to cancel the operation + /// Task with an iterator lazily populated with objects + /// When access or secret key is invalid + Task ListBucketsAsync(CancellationToken cancellationToken = default); + + /// + /// Check if a private bucket with the given name exists. + /// + /// BucketExistsArgs Arguments Object which has bucket identifier information - bucket name, region + /// Optional cancellation token to cancel the operation + /// Task + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + Task BucketExistsAsync(BucketExistsArgs args, CancellationToken cancellationToken = default); + + /// + /// Remove the bucket with the given name. + /// + /// RemoveBucketArgs Arguments Object which has bucket identifier information like bucket name .etc. + /// Optional cancellation token to cancel the operation + /// Task + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + Task RemoveBucketAsync(RemoveBucketArgs args, CancellationToken cancellationToken = default); + + /// + /// List all objects non-recursively in a bucket with a given prefix, optionally emulating a directory + /// + /// + /// ListObjectsArgs Arguments Object with information like Bucket name, prefix, recursive listing, + /// versioning + /// + /// Optional cancellation token to cancel the operation + /// An observable of items that client can subscribe to + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// + /// For example, if you call ListObjectsAsync on a bucket with versioning + /// enabled or object lock enabled + /// + IObservable ListObjectsAsync(ListObjectsArgs args, CancellationToken cancellationToken = default); + + /// + /// Gets notification configuration for this bucket + /// + /// GetBucketNotificationsArgs Arguments Object with information like Bucket name + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + Task GetBucketNotificationsAsync(GetBucketNotificationsArgs args, + CancellationToken cancellationToken = default); + + /// + /// Sets the notification configuration for this bucket + /// + /// + /// SetBucketNotificationsArgs Arguments Object with information like Bucket name, notification object + /// with configuration to set + /// + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When configuration XML provided is invalid + Task SetBucketNotificationsAsync(SetBucketNotificationsArgs args, CancellationToken cancellationToken = default); + + /// + /// Removes all bucket notification configurations stored on the server. + /// + /// RemoveAllBucketNotificationsArgs Arguments Object with information like Bucket name + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When configuration XML provided is invalid + Task RemoveAllBucketNotificationsAsync(RemoveAllBucketNotificationsArgs args, + CancellationToken cancellationToken = default); + + /// + /// Subscribes to bucket change notifications (a Minio-only extension) + /// + /// + /// ListenBucketNotificationsArgs Arguments Object with information like Bucket name, listen events, + /// prefix filter keys, suffix fileter keys + /// + /// Optional cancellation token to cancel the operation + /// An observable of JSON-based notification events + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When configuration XML provided is invalid + IObservable ListenBucketNotificationsAsync(ListenBucketNotificationsArgs args, + CancellationToken cancellationToken = default); + + /// + /// Gets Tagging values set for this bucket + /// + /// GetBucketTagsArgs Arguments Object with information like Bucket name + /// Optional cancellation token to cancel the operation + /// Tagging Object with key-value tag pairs + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When a functionality or extension is not implemented + Task GetBucketTagsAsync(GetBucketTagsArgs args, CancellationToken cancellationToken = default); + + /// + /// Sets the Tagging values for this bucket + /// + /// SetBucketTagsArgs Arguments Object with information like Bucket name, tag key-value pairs + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + Task SetBucketTagsAsync(SetBucketTagsArgs args, CancellationToken cancellationToken = default); + + /// + /// Removes Tagging values stored for the bucket. + /// + /// RemoveBucketTagsArgs Arguments Object with information like Bucket name + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + Task RemoveBucketTagsAsync(RemoveBucketTagsArgs args, CancellationToken cancellationToken = default); + + /// + /// Sets the Object Lock Configuration on this bucket + /// + /// + /// SetObjectLockConfigurationArgs Arguments Object with information like Bucket name, object lock + /// configuration to set + /// + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When object lock configuration on bucket is not set + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + Task SetObjectLockConfigurationAsync(SetObjectLockConfigurationArgs args, + CancellationToken cancellationToken = default); + + /// + /// Gets the Object Lock Configuration on this bucket + /// + /// GetObjectLockConfigurationArgs Arguments Object with information like Bucket name + /// Optional cancellation token to cancel the operation + /// ObjectLockConfiguration object + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When a functionality or extension is not implemented + /// When object lock configuration on bucket is not set + Task GetObjectLockConfigurationAsync(GetObjectLockConfigurationArgs args, + CancellationToken cancellationToken = default); + + /// + /// Removes the Object Lock Configuration on this bucket + /// + /// RemoveObjectLockConfigurationArgs Arguments Object with information like Bucket name + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When object lock configuration on bucket is not set + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + Task RemoveObjectLockConfigurationAsync(RemoveObjectLockConfigurationArgs args, + CancellationToken cancellationToken = default); + + /// + /// Get Versioning information on the bucket with given bucket name + /// + /// GetVersioningArgs takes bucket as argument. + /// Optional cancellation token to cancel the operation + /// GetVersioningResponse with information populated from REST response + /// When access or secret key is invalid + /// When bucket name is invalid + /// When a functionality or extension is not implemented + /// When bucket is not found + Task GetVersioningAsync(GetVersioningArgs args, + CancellationToken cancellationToken = default); + + /// + /// Set Versioning as specified on the bucket with given bucket name + /// + /// SetVersioningArgs Arguments Object with information like Bucket name, Versioning configuration + /// Optional cancellation token to cancel the operation + /// Task + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + Task SetVersioningAsync(SetVersioningArgs args, CancellationToken cancellationToken = default); + + /// + /// Sets the Encryption Configuration for the bucket. + /// + /// SetBucketEncryptionArgs Arguments Object with information like Bucket name, encryption config + /// Optional cancellation token to cancel the operation + /// Task + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + Task SetBucketEncryptionAsync(SetBucketEncryptionArgs args, CancellationToken cancellationToken = default); + + /// + /// Returns the Encryption Configuration for the bucket. + /// + /// GetBucketEncryptionArgs Arguments Object encapsulating information like Bucket name + /// Optional cancellation token to cancel the operation + /// An object of type ServerSideEncryptionConfiguration + /// When access or secret key is invalid + /// When bucket name is invalid + /// When a functionality or extension is not implemented + /// When bucket is not found + Task GetBucketEncryptionAsync(GetBucketEncryptionArgs args, + CancellationToken cancellationToken = default); + + /// + /// Removes the Encryption Configuration for the bucket. + /// + /// RemoveBucketEncryptionArgs Arguments Object encapsulating information like Bucket name + /// Optional cancellation token to cancel the operation + /// Task + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + Task RemoveBucketEncryptionAsync(RemoveBucketEncryptionArgs args, CancellationToken cancellationToken = default); + + /// + /// Sets the Lifecycle configuration for this bucket + /// + /// SetBucketLifecycleArgs Arguments Object with information like Bucket name, tag key-value pairs + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + Task SetBucketLifecycleAsync(SetBucketLifecycleArgs args, CancellationToken cancellationToken = default); + + /// + /// Gets Lifecycle configuration set for this bucket returned in an object + /// + /// GetBucketLifecycleArgs Arguments Object with information like Bucket name + /// Optional cancellation token to cancel the operation + /// Lifecycle Object with key-value tag pairs + /// When access or secret key is invalid + /// When bucket name is invalid + /// When a functionality or extension is not implemented + /// When bucket is not found + Task GetBucketLifecycleAsync(GetBucketLifecycleArgs args, + CancellationToken cancellationToken = default); + + /// + /// Removes Lifecycle configuration stored for the bucket. + /// + /// RemoveBucketLifecycleArgs Arguments Object with information like Bucket name + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When bucket is not found + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + Task RemoveBucketLifecycleAsync(RemoveBucketLifecycleArgs args, CancellationToken cancellationToken = default); + + /// + /// Gets Replication configuration set for this bucket + /// + /// GetBucketReplicationArgs Arguments Object with information like Bucket name + /// Optional cancellation token to cancel the operation + /// Replication configuration object + /// When access or secret key provided is invalid + /// When bucket name is invalid + /// When bucket replication configuration is not set + /// When a functionality or extension is not implemented + /// When bucket is not found + Task GetBucketReplicationAsync(GetBucketReplicationArgs args, + CancellationToken cancellationToken = default); + + /// + /// Sets the Replication configuration for this bucket + /// + /// + /// SetBucketReplicationArgs Arguments Object with information like Bucket name, Replication + /// Configuration object + /// + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key provided is invalid + /// When bucket name is invalid + /// When bucket replication configuration is not set + /// When a functionality or extension is not implemented + /// When bucket is not found + Task SetBucketReplicationAsync(SetBucketReplicationArgs args, CancellationToken cancellationToken = default); + + /// + /// Removes Replication configuration stored for the bucket. + /// + /// RemoveBucketReplicationArgs Arguments Object with information like Bucket name + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key provided is invalid + /// When bucket name is invalid + /// When bucket replication configuration is not set + /// When a functionality or extension is not implemented + /// When bucket is not found + Task RemoveBucketReplicationAsync(RemoveBucketReplicationArgs args, CancellationToken cancellationToken = default); + + Task GetPolicyAsync(GetPolicyArgs args, CancellationToken cancellationToken = default); + + IObservable ListenBucketNotificationsAsync(string bucketName, IList events, + string prefix = "", string suffix = "", CancellationToken cancellationToken = default); + + Task RemovePolicyAsync(RemovePolicyArgs args, CancellationToken cancellationToken = default); + + Task SetPolicyAsync(SetPolicyArgs args, CancellationToken cancellationToken = default); +} diff --git a/LibExternal/Minio/ApiEndpoints/IObjectOperations.cs b/LibExternal/Minio/ApiEndpoints/IObjectOperations.cs new file mode 100644 index 0000000..11e01ff --- /dev/null +++ b/LibExternal/Minio/ApiEndpoints/IObjectOperations.cs @@ -0,0 +1,361 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2017-2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.DataModel; +using Minio.DataModel.Args; +using Minio.DataModel.ObjectLock; +using Minio.DataModel.Response; +using Minio.DataModel.Select; +using Minio.DataModel.Tags; +using Minio.Exceptions; + +namespace Minio.ApiEndpoints; + +public interface IObjectOperations +{ + /// + /// Get the configuration object for Legal Hold Status + /// + /// + /// GetObjectLegalHoldArgs Arguments Object which has object identifier information - bucket name, + /// object name, version ID + /// + /// Optional cancellation token to cancel the operation + /// True if Legal Hold is ON, false otherwise + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When object lock configuration on bucket is not set + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + Task GetObjectLegalHoldAsync(GetObjectLegalHoldArgs args, CancellationToken cancellationToken = default); + + /// + /// Set the configuration for Legal Hold Status + /// + /// + /// SetObjectLegalHoldArgs Arguments Object which has object identifier information - bucket name, + /// object name, version ID and the status (ON/OFF) of legal-hold + /// + /// Optional cancellation token to cancel the operation + /// Task + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When object lock configuration on bucket is not set + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + Task SetObjectLegalHoldAsync(SetObjectLegalHoldArgs args, CancellationToken cancellationToken = default); + + /// + /// Set the Retention using the configuration object + /// + /// + /// SetObjectRetentionArgs Arguments Object which has object identifier information - bucket name, + /// object name, version ID + /// + /// Optional cancellation token to cancel the operation + /// Task + /// When access or secret key provided is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When object lock configuration on bucket is not set + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + Task SetObjectRetentionAsync(SetObjectRetentionArgs args, CancellationToken cancellationToken = default); + + /// + /// Get the Retention configuration for the object + /// + /// + /// GetObjectRetentionArgs Arguments Object which has object identifier information - bucket name, + /// object name, version ID + /// + /// Optional cancellation token to cancel the operation + /// ObjectRetentionConfiguration object which contains the Retention configuration + /// When access or secret key provided is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When a functionality or extension is not implemented + /// When object lock configuration on bucket is not set + Task GetObjectRetentionAsync(GetObjectRetentionArgs args, + CancellationToken cancellationToken = default); + + /// + /// Clears the Retention configuration for the object + /// + /// + /// ClearObjectRetentionArgs Arguments Object which has object identifier information - bucket name, + /// object name, version ID + /// + /// Optional cancellation token to cancel the operation + /// Task + /// When access or secret key provided is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When object lock configuration on bucket is not set + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + Task ClearObjectRetentionAsync(ClearObjectRetentionArgs args, CancellationToken cancellationToken = default); + + /// + /// Removes an object with given name in specific bucket + /// + /// + /// RemoveObjectArgs Arguments Object encapsulates information like - bucket name, object name, whether + /// delete all versions + /// + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key provided is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + Task RemoveObjectAsync(RemoveObjectArgs args, CancellationToken cancellationToken = default); + + /// + /// Removes list of objects from bucket + /// + /// + /// RemoveObjectsArgs Arguments Object encapsulates information like - bucket name, List of objects, + /// optional list of versions (for each object) to be deleted + /// + /// Optional cancellation token to cancel the operation + /// Observable that returns delete error while deleting objects if any + /// When access or secret key provided is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When a functionality or extension is not implemented + Task> RemoveObjectsAsync(RemoveObjectsArgs args, + CancellationToken cancellationToken = default); + + /// + /// Copy a source object into a new destination object. + /// + /// + /// CopyObjectArgs Arguments Object which encapsulates bucket name, object name, destination bucket, + /// destination object names, Copy conditions object, metadata, SSE source, destination objects + /// + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// For encrypted copy operation, Access is denied if the key is wrong + Task CopyObjectAsync(CopyObjectArgs args, CancellationToken cancellationToken = default); + + /// + /// Get an object. The object will be streamed to the callback given by the user. + /// + /// + /// GetObjectArgs Arguments Object encapsulates information like - bucket name, object name, server-side + /// encryption object, action stream, length, offset + /// + /// Optional cancellation token to cancel the operation + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// If the directory to copy to is not found + Task GetObjectAsync(GetObjectArgs args, CancellationToken cancellationToken = default); + + /// + /// Creates object in a bucket fom input stream or filename. + /// + /// + /// PutObjectArgs Arguments object encapsulating bucket name, object name, file name, object data + /// stream, object size, content type. + /// + /// Optional cancellation token to cancel the operation + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// If the file to copy from not found + /// The file stream has been disposed + /// The file stream cannot be read from + /// The file stream is currently in a read operation + /// For encrypted PUT operation, Access is denied if the key is wrong + Task PutObjectAsync(PutObjectArgs args, CancellationToken cancellationToken = default); + + /// + /// Select an object's content. The object will be streamed to the callback given by the user. + /// + /// + /// SelectObjectContentArgs Arguments Object which encapsulates bucket name, object name, Select Object + /// Options + /// + /// Optional cancellation token to cancel the operation + /// When access or secret key provided is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + Task SelectObjectContentAsync(SelectObjectContentArgs args, + CancellationToken cancellationToken = default); + + /// + /// Lists all incomplete uploads in a given bucket and prefix recursively + /// + /// ListIncompleteUploadsArgs Arguments Object which encapsulates bucket name, prefix, recursive + /// Optional cancellation token to cancel the operation + /// A lazily populated list of incomplete uploads + /// When access or secret key provided is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + IObservable ListIncompleteUploads(ListIncompleteUploadsArgs args, + CancellationToken cancellationToken = default); + + /// + /// Remove incomplete uploads from a given bucket and objectName + /// + /// RemoveIncompleteUploadArgs Arguments Object which encapsulates bucket, object names + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When configuration XML provided is invalid + Task RemoveIncompleteUploadAsync(RemoveIncompleteUploadArgs args, CancellationToken cancellationToken = default); + + /// + /// Presigned get url - returns a presigned url to access an object's data without credentials.URL can have a maximum + /// expiry of + /// up to 7 days or a minimum of 1 second.Additionally, you can override a set of response headers using reqParams. + /// + /// + /// PresignedGetObjectArgs Arguments object encapsulating bucket and object names, expiry time, response + /// headers, request date + /// + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + Task PresignedGetObjectAsync(PresignedGetObjectArgs args); + + /// + /// Presigned post policy + /// + /// PresignedPostPolicyArgs Arguments object encapsulating Policy, Expiry, Region, + /// Tuple of URI and Policy Form data + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When configuration XML provided is invalid + Task<(Uri, IDictionary)> PresignedPostPolicyAsync(PresignedPostPolicyArgs args); + + /// + /// Presigned Put url -returns a presigned url to upload an object without credentials.URL can have a maximum expiry of + /// upto 7 days or a minimum of 1 second. + /// + /// PresignedPutObjectArgs Arguments Object which encapsulates bucket, object names, expiry + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When configuration XML provided is invalid + Task PresignedPutObjectAsync(PresignedPutObjectArgs args); + + /// + /// Tests the object's existence and returns metadata about existing objects. + /// + /// + /// StatObjectArgs Arguments Object encapsulates information like - bucket name, object name, + /// server-side encryption object + /// + /// Optional cancellation token to cancel the operation + /// Facts about the object + Task StatObjectAsync(StatObjectArgs args, CancellationToken cancellationToken = default); + + /// + /// Presigned post policy + /// + /// + /// + Task<(Uri, IDictionary)> PresignedPostPolicyAsync(PostPolicy policy); + + /// + /// Gets Tagging values set for this object + /// + /// GetObjectTagsArgs Arguments Object with information like Bucket, Object name, (optional)version Id + /// Optional cancellation token to cancel the operation + /// Tagging Object with key-value tag pairs + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When a functionality or extension is not implemented + /// When object is not found + Task GetObjectTagsAsync(GetObjectTagsArgs args, CancellationToken cancellationToken = default); + + /// + /// Sets the Tagging values for this object + /// + /// + /// SetObjectTagsArgs Arguments Object with information like Bucket name,Object name, (optional)version + /// Id, tag key-value pairs + /// + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + Task SetObjectTagsAsync(SetObjectTagsArgs args, CancellationToken cancellationToken = default); + + /// + /// Removes Tagging values stored for the object + /// + /// RemoveObjectTagsArgs Arguments Object with information like Bucket name + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When a functionality or extension is not implemented + /// When object is not found + /// When configuration XML provided is invalid + Task RemoveObjectTagsAsync(RemoveObjectTagsArgs args, CancellationToken cancellationToken = default); +} diff --git a/LibExternal/Minio/ApiEndpoints/ObjectOperations.cs b/LibExternal/Minio/ApiEndpoints/ObjectOperations.cs new file mode 100644 index 0000000..e0b6b90 --- /dev/null +++ b/LibExternal/Minio/ApiEndpoints/ObjectOperations.cs @@ -0,0 +1,1142 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2017-2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Reactive.Linq; +using Minio.ApiEndpoints; +using Minio.DataModel; +using Minio.DataModel.Args; +using Minio.DataModel.Encryption; +using Minio.DataModel.ObjectLock; +using Minio.DataModel.Response; +using Minio.DataModel.Result; +using Minio.DataModel.Select; +using Minio.DataModel.Tags; +using Minio.Exceptions; +using Minio.Helper; + +namespace Minio; + +[SuppressMessage("Design", "MA0048:File name must match type name", Justification = "Split up in partial classes")] +public partial class MinioClient : IObjectOperations +{ + /// + /// Get an object. The object will be streamed to the callback given by the user. + /// + /// + /// GetObjectArgs Arguments Object encapsulates information like - bucket name, object name, server-side + /// encryption object, action stream, length, offset + /// + /// Optional cancellation token to cancel the operation + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// If the directory to copy to is not found + public Task GetObjectAsync(GetObjectArgs args, CancellationToken cancellationToken = default) + { + return GetObjectHelper(args, cancellationToken); + } + + /// + /// Select an object's content. The object will be streamed to the callback given by the user. + /// + /// + /// SelectObjectContentArgs Arguments Object which encapsulates bucket name, object name, Select Object + /// Options + /// + /// Optional cancellation token to cancel the operation + /// When access or secret key provided is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + public async Task SelectObjectContentAsync(SelectObjectContentArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + var selectObjectContentResponse = + new SelectObjectContentResponse(response.StatusCode, response.Content, response.ContentBytes); + return selectObjectContentResponse.ResponseStream; + } + + /// + /// Lists all incomplete uploads in a given bucket and prefix recursively + /// + /// ListIncompleteUploadsArgs Arguments Object which encapsulates bucket name, prefix, recursive + /// Optional cancellation token to cancel the operation + /// A lazily populated list of incomplete uploads + /// When access or secret key provided is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + public IObservable ListIncompleteUploads(ListIncompleteUploadsArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + return Observable.Create( + async obs => + { + string nextKeyMarker = null; + string nextUploadIdMarker = null; + var isRunning = true; + + while (isRunning) + { + var getArgs = new GetMultipartUploadsListArgs() + .WithBucket(args.BucketName) + .WithDelimiter(args.Delimiter) + .WithPrefix(args.Prefix) + .WithKeyMarker(nextKeyMarker) + .WithUploadIdMarker(nextUploadIdMarker); + var uploads = await GetMultipartUploadsListAsync(getArgs, cancellationToken).ConfigureAwait(false); + if (uploads is null) + { + isRunning = false; + continue; + } + + foreach (var upload in uploads.Item2) obs.OnNext(upload); + nextKeyMarker = uploads.Item1.NextKeyMarker; + nextUploadIdMarker = uploads.Item1.NextUploadIdMarker; + isRunning = uploads.Item1.IsTruncated; + } + }); + } + + /// + /// Remove incomplete uploads from a given bucket and objectName + /// + /// RemoveIncompleteUploadArgs Arguments Object which encapsulates bucket, object names + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When configuration XML provided is invalid + public async Task RemoveIncompleteUploadAsync(RemoveIncompleteUploadArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + var listUploadArgs = new ListIncompleteUploadsArgs() + .WithBucket(args.BucketName) + .WithPrefix(args.ObjectName); + + Upload[] uploads; + try + { + uploads = await ListIncompleteUploads(listUploadArgs, cancellationToken)?.ToArray(); + } + catch (Exception ex) when (ex.GetType() == typeof(BucketNotFoundException)) + { + throw; + } + + if (uploads is null) return; + foreach (var upload in uploads) + if (upload.Key.Equals(args.ObjectName, StringComparison.OrdinalIgnoreCase)) + { + var rmArgs = new RemoveUploadArgs() + .WithBucket(args.BucketName) + .WithObject(args.ObjectName) + .WithUploadId(upload.UploadId); + await RemoveUploadAsync(rmArgs, cancellationToken).ConfigureAwait(false); + } + } + + /// + /// Presigned get url - returns a presigned url to access an object's data without credentials.URL can have a maximum + /// expiry of + /// up to 7 days or a minimum of 1 second.Additionally, you can override a set of response headers using reqParams. + /// + /// + /// PresignedGetObjectArgs Arguments object encapsulating bucket and object names, expiry time, response + /// headers, request date + /// + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + public async Task PresignedGetObjectAsync(PresignedGetObjectArgs args) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + var authenticator = new V4Authenticator(Config.Secure, Config.AccessKey, Config.SecretKey, Config.Region, + Config.SessionToken); + return authenticator.PresignURL(requestMessageBuilder, args.Expiry, Config.Region, Config.SessionToken, + args.RequestDate); + } + + /// + /// Presigned post policy + /// + /// PresignedPostPolicyArgs Arguments object encapsulating Policy, Expiry, Region, + /// Tuple of URI and Policy Form data + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When configuration XML provided is invalid + public async Task<(Uri, IDictionary)> PresignedPostPolicyAsync(PresignedPostPolicyArgs args) + { + if (args is null) + throw new ArgumentNullException(nameof(args)); + + // string region = string.Empty; + var region = await this.GetRegion(args.BucketName).ConfigureAwait(false); + args.Validate(); + // Presigned operations are not allowed for anonymous users + if (string.IsNullOrEmpty(Config.AccessKey) && string.IsNullOrEmpty(Config.SecretKey)) + throw new MinioException("Presigned operations are not supported for anonymous credentials"); + + var authenticator = new V4Authenticator(Config.Secure, Config.AccessKey, Config.SecretKey, + region, Config.SessionToken); + + // Get base64 encoded policy. + var policyBase64 = args.Policy.Base64(); + + var t = DateTime.UtcNow; + const string signV4Algorithm = "AWS4-HMAC-SHA256"; + var credential = authenticator.GetCredentialString(t, region); + var signature = authenticator.PresignPostSignature(region, t, policyBase64); + args = args.WithDate(t) + .WithAlgorithm(signV4Algorithm) + .WithSessionToken(Config.SessionToken) + .WithCredential(credential) + .WithRegion(region); + + // Fill in the form data. + args.Policy.FormData["bucket"] = args.BucketName; + // args.Policy.formData["key"] = "\\\"" + args.ObjectName + "\\\""; + + args.Policy.FormData["key"] = args.ObjectName; + + args.Policy.FormData["policy"] = policyBase64; + args.Policy.FormData["x-amz-algorithm"] = signV4Algorithm; + args.Policy.FormData["x-amz-credential"] = credential; + args.Policy.FormData["x-amz-date"] = t.ToString("yyyyMMddTHHmmssZ", CultureInfo.InvariantCulture); + if (!string.IsNullOrEmpty(Config.SessionToken)) + args.Policy.FormData["x-amz-security-token"] = Config.SessionToken; + args.Policy.FormData["x-amz-signature"] = signature; + + Config.Uri = RequestUtil.MakeTargetURL(Config.BaseUrl, Config.Secure, args.BucketName, region, false); + return (Config.Uri, args.Policy.FormData); + } + + /// + /// Presigned Put url -returns a presigned url to upload an object without credentials.URL can have a maximum expiry of + /// upto 7 days or a minimum of 1 second. + /// + /// PresignedPutObjectArgs Arguments Object which encapsulates bucket, object names, expiry + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When configuration XML provided is invalid + public async Task PresignedPutObjectAsync(PresignedPutObjectArgs args) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(HttpMethod.Put, args.BucketName, + args.ObjectName, + args.Headers, // contentType + Convert.ToString(args.GetType(), CultureInfo.InvariantCulture), // metaData + Utils.ObjectToByteArray(args.RequestBody)).ConfigureAwait(false); + var authenticator = new V4Authenticator(Config.Secure, Config.AccessKey, Config.SecretKey, Config.Region, + Config.SessionToken); + return authenticator.PresignURL(requestMessageBuilder, args.Expiry, Config.Region, Config.SessionToken); + } + + /// + /// Get the configuration object for Legal Hold Status + /// + /// + /// GetObjectLegalHoldArgs Arguments Object which has object identifier information - bucket name, + /// object name, version ID + /// + /// Optional cancellation token to cancel the operation + /// True if Legal Hold is ON, false otherwise + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When object lock configuration on bucket is not set + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + public async Task GetObjectLegalHoldAsync(GetObjectLegalHoldArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + var legalHoldConfig = new GetLegalHoldResponse(response.StatusCode, response.Content); + return legalHoldConfig.CurrentLegalHoldConfiguration?.Status.Equals("on", StringComparison.OrdinalIgnoreCase) == + true; + } + + /// + /// Set the Legal Hold Status using the related configuration + /// + /// + /// SetObjectLegalHoldArgs Arguments Object which has object identifier information - bucket name, + /// object name, version ID + /// + /// Optional cancellation token to cancel the operation + /// Task + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When object lock configuration on bucket is not set + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + public async Task SetObjectLegalHoldAsync(SetObjectLegalHoldArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Gets Tagging values set for this object + /// + /// GetObjectTagsArgs Arguments Object with information like Bucket, Object name, (optional)version Id + /// Optional cancellation token to cancel the operation + /// Tagging Object with key-value tag pairs + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When a functionality or extension is not implemented + public async Task GetObjectTagsAsync(GetObjectTagsArgs args, CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + var getObjectTagsResponse = new GetObjectTagsResponse(response.StatusCode, response.Content); + return getObjectTagsResponse.ObjectTags; + } + + /// + /// Removes an object with given name in specific bucket + /// + /// + /// RemoveObjectArgs Arguments Object encapsulates information like - bucket name, object name, optional + /// list of versions to be deleted + /// + /// Optional cancellation token to cancel the operation + /// Task + /// When access or secret key provided is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When configuration XML provided is invalid + public async Task RemoveObjectAsync(RemoveObjectArgs args, CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var restResponse = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Removes list of objects from bucket + /// + /// + /// RemoveObjectsArgs Arguments Object encapsulates information like - bucket name, List of objects, + /// optional list of versions (for each object) to be deleted + /// + /// Optional cancellation token to cancel the operation + /// Observable that returns delete error while deleting objects if any + /// When access or secret key provided is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + public async Task> RemoveObjectsAsync(RemoveObjectsArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + IList errs = new List(); + errs = args.ObjectNamesVersions.Count > 0 + ? await RemoveObjectVersionsHelper(args, errs.ToList(), cancellationToken).ConfigureAwait(false) + : await RemoveObjectsHelper(args, errs, cancellationToken).ConfigureAwait(false); + + return Observable.Create( // From Current change + async obs => + { + await Task.Yield(); + foreach (var error in errs) obs.OnNext(error); + } + ); + } + + /// + /// Sets the Tagging values for this object + /// + /// + /// SetObjectTagsArgs Arguments Object with information like Bucket name,Object name, (optional)version + /// Id, tag key-value pairs + /// + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + public async Task SetObjectTagsAsync(SetObjectTagsArgs args, CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Removes Tagging values stored for the object + /// + /// RemoveObjectTagsArgs Arguments Object with information like Bucket name + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + public async Task RemoveObjectTagsAsync(RemoveObjectTagsArgs args, CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Set the Retention using the configuration object + /// + /// + /// SetObjectRetentionArgs Arguments Object which has object identifier information - bucket name, + /// object name, version ID + /// + /// Optional cancellation token to cancel the operation + /// Task + /// When access or secret key provided is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When object lock configuration on bucket is not set + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + public async Task SetObjectRetentionAsync(SetObjectRetentionArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Get the Retention configuration for the object + /// + /// + /// GetObjectRetentionArgs Arguments Object which has object identifier information - bucket name, + /// object name, version ID + /// + /// Optional cancellation token to cancel the operation + /// Task + /// When access or secret key provided is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When a functionality or extension is not implemented + /// When object lock configuration on bucket is not set + public async Task GetObjectRetentionAsync(GetObjectRetentionArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + var retentionResponse = new GetRetentionResponse(response.StatusCode, response.Content); + return retentionResponse.CurrentRetentionConfiguration; + } + + /// + /// Clears the Retention configuration for the object + /// + /// + /// ClearObjectRetentionArgs Arguments Object which has object identifier information - bucket name, + /// object name, version ID + /// + /// Optional cancellation token to cancel the operation + /// Task + /// When access or secret key provided is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When object lock configuration on bucket is not set + /// When a functionality or extension is not implemented + /// When configuration XML provided is invalid + public async Task ClearObjectRetentionAsync(ClearObjectRetentionArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Creates object in a bucket fom input stream or filename. + /// + /// + /// PutObjectArgs Arguments object encapsulating bucket name, object name, file name, object data + /// stream, object size, content type. + /// + /// Optional cancellation token to cancel the operation + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// If the file to copy from not found + /// The file stream has been disposed + /// The file stream cannot be read from + /// The file stream is currently in a read operation + /// For encrypted PUT operation, Access is denied if the key is wrong + public async Task PutObjectAsync(PutObjectArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + args.SSE?.Marshal(args.Headers); + + var isSnowball = args.Headers.ContainsKey("X-Amz-Meta-Snowball-Auto-Extract") && + Convert.ToBoolean(args.Headers["X-Amz-Meta-Snowball-Auto-Extract"], + CultureInfo.InvariantCulture); + + // Upload object in single part if size falls under restricted part size + // or the request has snowball objects + if ((args.ObjectSize < Constants.MinimumPartSize || isSnowball) && args.ObjectSize >= 0 && + args.ObjectStreamData is not null) + { + var bytes = await ReadFullAsync(args.ObjectStreamData, (int)args.ObjectSize).ConfigureAwait(false); + var bytesRead = bytes.Length; + if (bytesRead != (int)args.ObjectSize) + throw new UnexpectedShortReadException( + $"Data read {bytesRead.ToString(CultureInfo.InvariantCulture)} is shorter than the size {args.ObjectSize.ToString(CultureInfo.InvariantCulture)} of input buffer."); + + args = args.WithRequestBody(bytes) + .WithStreamData(null) + .WithObjectSize(bytesRead); + return await PutObjectSinglePartAsync(args, cancellationToken).ConfigureAwait(false); + } + + // For all sizes greater than 5MiB do multipart. + var multipartUploadArgs = new NewMultipartUploadPutArgs() + .WithBucket(args.BucketName) + .WithObject(args.ObjectName) + .WithVersionId(args.VersionId) + .WithHeaders(args.Headers) + .WithContentType(args.ContentType) + .WithTagging(args.ObjectTags) + .WithLegalHold(args.LegalHoldEnabled) + .WithRetentionConfiguration(args.Retention) + .WithServerSideEncryption(args.SSE); + // Get upload Id after creating new multi-part upload operation to + // be used in putobject part, complete multipart upload operations. + var uploadId = await NewMultipartUploadAsync(multipartUploadArgs, cancellationToken).ConfigureAwait(false); + // Remove SSE-S3 and KMS headers during PutObjectPart operations. + var putObjectPartArgs = new PutObjectPartArgs() + .WithBucket(args.BucketName) + .WithObject(args.ObjectName) + .WithObjectSize(args.ObjectSize) + .WithContentType(args.ContentType) + .WithUploadId(uploadId) + .WithStreamData(args.ObjectStreamData) + .WithProgress(args.Progress) + .WithRequestBody(args.RequestBody) + .WithHeaders(args.Headers); + IDictionary etags = null; + // Upload file contents. + if (!string.IsNullOrEmpty(args.FileName)) + { + using var fileStream = new FileStream(args.FileName, FileMode.Open, FileAccess.Read); + putObjectPartArgs = putObjectPartArgs + .WithStreamData(fileStream) + .WithObjectSize(fileStream.Length) + .WithRequestBody(null); + etags = await PutObjectPartAsync(putObjectPartArgs, cancellationToken).ConfigureAwait(false); + } + // Upload stream contents + else + { + etags = await PutObjectPartAsync(putObjectPartArgs, cancellationToken).ConfigureAwait(false); + } + + var completeMultipartUploadArgs = new CompleteMultipartUploadArgs() + .WithBucket(args.BucketName) + .WithObject(args.ObjectName) + .WithUploadId(uploadId) + .WithETags(etags); + var putObjectResponse = await CompleteMultipartUploadAsync(completeMultipartUploadArgs, cancellationToken) + .ConfigureAwait(false); + putObjectResponse.Size = args.ObjectSize; + return putObjectResponse; + } + + /// + /// Copy a source object into a new destination object. + /// + /// + /// CopyObjectArgs Arguments Object which encapsulates bucket name, object name, destination bucket, + /// destination object names, Copy conditions object, metadata, SSE source, destination objects + /// + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// For encrypted copy operation, Access is denied if the key is wrong + public async Task CopyObjectAsync(CopyObjectArgs args, CancellationToken cancellationToken = default) + { + if (args is null) + throw new ArgumentNullException(nameof(args)); + + IServerSideEncryption sseGet = null; + if (args.SourceObject.SSE is SSECopy sSECopy) sseGet = sSECopy.CloneToSSEC(); + + var statArgs = new StatObjectArgs() + .WithBucket(args.SourceObject.BucketName) + .WithObject(args.SourceObject.ObjectName) + .WithVersionId(args.SourceObject.VersionId) + .WithServerSideEncryption(sseGet); + var stat = await StatObjectAsync(statArgs, cancellationToken).ConfigureAwait(false); + _ = args.WithCopyObjectSourceStats(stat); + if (stat.TaggingCount > 0 && !args.ReplaceTagsDirective) + { + var getTagArgs = new GetObjectTagsArgs() + .WithBucket(args.SourceObject.BucketName) + .WithObject(args.SourceObject.ObjectName) + .WithVersionId(args.SourceObject.VersionId) + .WithServerSideEncryption(sseGet); + var tag = await GetObjectTagsAsync(getTagArgs, cancellationToken).ConfigureAwait(false); + _ = args.WithTagging(tag); + } + + args.Validate(); + var srcByteRangeSize = args.SourceObject.CopyOperationConditions?.ByteRange ?? 0L; + var copySize = srcByteRangeSize == 0 ? args.SourceObjectInfo.Size : srcByteRangeSize; + + if (srcByteRangeSize > args.SourceObjectInfo.Size || + (srcByteRangeSize > 0 && + args.SourceObject.CopyOperationConditions.byteRangeEnd >= + args.SourceObjectInfo.Size)) + throw new InvalidDataException( + $"Specified byte range ({args.SourceObject.CopyOperationConditions.byteRangeStart.ToString(CultureInfo.InvariantCulture)}-{args.SourceObject.CopyOperationConditions.byteRangeEnd.ToString(CultureInfo.InvariantCulture)}) does not fit within source object (size={args.SourceObjectInfo.Size.ToString(CultureInfo.InvariantCulture)})"); + + if (copySize > Constants.MaxSingleCopyObjectSize || + (srcByteRangeSize > 0 && + srcByteRangeSize != args.SourceObjectInfo.Size)) + { + var multiArgs = new MultipartCopyUploadArgs(args) + .WithCopySize(copySize); + await MultipartCopyUploadAsync(multiArgs, cancellationToken).ConfigureAwait(false); + } + else + { + var sourceObject = new CopySourceObjectArgs() + .WithBucket(args.SourceObject.BucketName) + .WithObject(args.SourceObject.ObjectName) + .WithVersionId(args.SourceObject.VersionId) + .WithCopyConditions(args.SourceObject.CopyOperationConditions); + + var cpReqArgs = new CopyObjectRequestArgs() + .WithBucket(args.BucketName) + .WithObject(args.ObjectName) + .WithVersionId(args.VersionId) + .WithHeaders(args.Headers) + .WithCopyObjectSource(sourceObject) + .WithSourceObjectInfo(args.SourceObjectInfo) + .WithCopyOperationObjectType(typeof(CopyObjectResult)) + .WithReplaceMetadataDirective(args.ReplaceMetadataDirective) + .WithReplaceTagsDirective(args.ReplaceTagsDirective) + .WithTagging(args.ObjectTags); + cpReqArgs.Validate(); + var newMeta = args.ReplaceMetadataDirective + ? new Dictionary(args.Headers, StringComparer.Ordinal) + : new Dictionary(args.SourceObjectInfo.MetaData, StringComparer.Ordinal); + if (args.SourceObject.SSE is not null and SSECopy) + args.SourceObject.SSE.Marshal(newMeta); + args.SSE?.Marshal(newMeta); + _ = cpReqArgs.WithHeaders(newMeta); + _ = await CopyObjectRequestAsync(cpReqArgs, cancellationToken).ConfigureAwait(false); + } + } + + /// + /// Presigned post policy + /// + /// + /// + public Task<(Uri, IDictionary)> PresignedPostPolicyAsync(PostPolicy policy) + { + if (policy is null) + throw new ArgumentNullException(nameof(policy)); + + var args = new PresignedPostPolicyArgs() + .WithBucket(policy.Bucket) + .WithObject(policy.Key) + .WithPolicy(policy); + return PresignedPostPolicyAsync(args); + } + + /// + /// Tests the object's existence and returns metadata about existing objects. + /// + /// + /// StatObjectArgs Arguments Object encapsulates information like - bucket name, object name, + /// server-side encryption object + /// + /// Optional cancellation token to cancel the operation + /// Facts about the object + public async Task StatObjectAsync(StatObjectArgs args, CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + var responseHeaders = new Dictionary(StringComparer.Ordinal); + foreach (var param in response.Headers.ToList()) responseHeaders.Add(param.Key, param.Value); + var statResponse = new StatObjectResponse(response.StatusCode, response.Content, response.Headers, args); + + return statResponse.ObjectInfo; + } + + /// + /// Get list of multi-part uploads matching particular uploadIdMarker + /// + /// GetMultipartUploadsListArgs Arguments Object which encapsulates bucket name, prefix, recursive + /// Optional cancellation token to cancel the operation + /// + private async Task>> GetMultipartUploadsListAsync( + GetMultipartUploadsListArgs args, + CancellationToken cancellationToken) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + var getUploadResponse = new GetMultipartUploadsListResponse(response.StatusCode, response.Content); + + return getUploadResponse.UploadResult; + } + + /// + /// Remove object with matching uploadId from bucket + /// + /// RemoveUploadArgs Arguments Object which encapsulates bucket, object names, upload Id + /// Optional cancellation token to cancel the operation + /// + private async Task RemoveUploadAsync(RemoveUploadArgs args, CancellationToken cancellationToken) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Upload object part to bucket for particular uploadId + /// + /// + /// PutObjectArgs encapsulates bucket name, object name, upload id, part number, object data(body), + /// Headers, SSE Headers + /// + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// The file stream has been disposed + /// The file stream cannot be read from + /// The file stream is currently in a read operation + /// For encrypted PUT operation, Access is denied if the key is wrong + private async Task PutObjectSinglePartAsync(PutObjectArgs args, + CancellationToken cancellationToken = default) + { + //Skipping validate as we need the case where stream sends 0 bytes + var progressReport = new ProgressReport(); + args.Progress?.Report(progressReport); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + + progressReport.Percentage = 100; + progressReport.TotalBytesTransferred = args.ObjectSize; + args.Progress?.Report(progressReport); + return new PutObjectResponse(response.StatusCode, response.Content, response.Headers, + args.ObjectSize, args.ObjectName); + } + + /// + /// Upload object in multiple parts. Private Helper function + /// + /// PutObjectPartArgs encapsulates bucket name, object name, upload id, part number, object data(body) + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// The file stream has been disposed + /// The file stream cannot be read from + /// The file stream is currently in a read operation + /// For encrypted PUT operation, Access is denied if the key is wrong + private async Task> PutObjectPartAsync(PutObjectPartArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + var multiPartInfo = Utils.CalculateMultiPartSize(args.ObjectSize); + var partSize = multiPartInfo.PartSize; + var partCount = multiPartInfo.PartCount; + var lastPartSize = multiPartInfo.LastPartSize; + var totalParts = new Part[(int)partCount]; + + var expectedReadSize = partSize; + int partNumber; + var numPartsUploaded = 0; + var etags = new Dictionary(); + var progressReport = new ProgressReport(); + args.Progress?.Report(progressReport); + for (partNumber = 1; partNumber <= partCount; partNumber++) + { + var dataToCopy = await ReadFullAsync(args.ObjectStreamData, (int)partSize).ConfigureAwait(false); + if (dataToCopy.IsEmpty && numPartsUploaded > 0) break; + if (partNumber == partCount) expectedReadSize = lastPartSize; + var putObjectArgs = new PutObjectArgs(args) + .WithRequestBody(dataToCopy) + .WithUploadId(args.UploadId) + .WithPartNumber(partNumber); + var putObjectResponse = + await PutObjectSinglePartAsync(putObjectArgs, cancellationToken).ConfigureAwait(false); + var etag = putObjectResponse.Etag; + + numPartsUploaded++; + totalParts[partNumber - 1] = new Part + { + PartNumber = partNumber, ETag = etag, Size = (long)expectedReadSize + }; + etags[partNumber] = etag; + if (!dataToCopy.IsEmpty) progressReport.TotalBytesTransferred += dataToCopy.Length; + if (args.ObjectSize != -1) progressReport.Percentage = (int)(100 * partNumber / partCount); + args.Progress?.Report(progressReport); + } + + // This shouldn't happen where stream size is known. + if (partCount != numPartsUploaded && args.ObjectSize != -1) + { + var removeUploadArgs = new RemoveUploadArgs() + .WithBucket(args.BucketName) + .WithObject(args.ObjectName) + .WithUploadId(args.UploadId); + await RemoveUploadAsync(removeUploadArgs, cancellationToken).ConfigureAwait(false); + return null; + } + + if (args.ObjectSize == -1) + { + progressReport.Percentage = 100; + args.Progress?.Report(progressReport); + } + + return etags; + } + + /// + /// Make a multi part copy upload for objects larger than 5GB or if CopyCondition specifies a byte range. + /// + /// + /// MultipartCopyUploadArgs Arguments object encapsulating destination and source bucket, object names, + /// copy conditions, size, metadata, SSE + /// + /// Optional cancellation token to cancel the operation + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// For encrypted copy operation, Access is denied if the key is wrong + private async Task MultipartCopyUploadAsync(MultipartCopyUploadArgs args, + CancellationToken cancellationToken = default) + { + var multiPartInfo = Utils.CalculateMultiPartSize(args.CopySize, true); + var partSize = multiPartInfo.PartSize; + var partCount = multiPartInfo.PartCount; + var lastPartSize = multiPartInfo.LastPartSize; + var totalParts = new Part[(int)partCount]; + + var nmuArgs = new NewMultipartUploadCopyArgs() + .WithBucket(args.BucketName) + .WithObject(args.ObjectName ?? args.SourceObject.ObjectName) + .WithHeaders(args.Headers) + .WithCopyObjectSource(args.SourceObject) + .WithSourceObjectInfo(args.SourceObjectInfo) + .WithReplaceMetadataDirective(args.ReplaceMetadataDirective) + .WithReplaceTagsDirective(args.ReplaceTagsDirective); + nmuArgs.Validate(); + // No need to resume upload since this is a Server-side copy. Just initiate a new upload. + var uploadId = await NewMultipartUploadAsync(nmuArgs, cancellationToken).ConfigureAwait(false); + var expectedReadSize = partSize; + int partNumber; + for (partNumber = 1; partNumber <= partCount; partNumber++) + { + var partCondition = args.SourceObject.CopyOperationConditions.Clone(); + partCondition.byteRangeStart = ((long)partSize * (partNumber - 1)) + partCondition.byteRangeStart; + partCondition.byteRangeEnd = partNumber < partCount + ? partCondition.byteRangeStart + (long)partSize - 1 + : partCondition.byteRangeStart + (long)lastPartSize - 1; + var queryMap = new Dictionary(StringComparer.Ordinal); + if (!string.IsNullOrEmpty(uploadId) && partNumber > 0) + { + queryMap.Add("uploadId", uploadId); + queryMap.Add("partNumber", partNumber.ToString(CultureInfo.InvariantCulture)); + } + + if (args.SourceObject.SSE is not null and SSECopy) + args.SourceObject.SSE.Marshal(args.Headers); + args.SSE?.Marshal(args.Headers); + var cpPartArgs = new CopyObjectRequestArgs() + .WithBucket(args.BucketName) + .WithObject(args.ObjectName) + .WithVersionId(args.VersionId) + .WithHeaders(args.Headers) + .WithCopyOperationObjectType(typeof(CopyPartResult)) + .WithPartCondition(partCondition) + .WithQueryMap(queryMap) + .WithCopyObjectSource(args.SourceObject) + .WithSourceObjectInfo(args.SourceObjectInfo) + .WithReplaceMetadataDirective(args.ReplaceMetadataDirective) + .WithReplaceTagsDirective(args.ReplaceTagsDirective) + .WithTagging(args.ObjectTags); + var cpPartResult = + (CopyPartResult)await CopyObjectRequestAsync(cpPartArgs, cancellationToken).ConfigureAwait(false); + + totalParts[partNumber - 1] = new Part + { + PartNumber = partNumber, ETag = cpPartResult.ETag, Size = (long)expectedReadSize + }; + } + + var etags = new Dictionary(); + for (partNumber = 1; partNumber <= partCount; partNumber++) etags[partNumber] = totalParts[partNumber - 1].ETag; + var completeMultipartUploadArgs = new CompleteMultipartUploadArgs(args) + .WithUploadId(uploadId) + .WithETags(etags); + // Complete multi part upload + _ = await CompleteMultipartUploadAsync(completeMultipartUploadArgs, cancellationToken).ConfigureAwait(false); + } + + /// + /// Start a new multi-part upload request + /// + /// + /// NewMultipartUploadPutArgs arguments object encapsulating bucket name, object name, Headers, SSE + /// Headers + /// + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// For encrypted copy operation, Access is denied if the key is wrong + private async Task NewMultipartUploadAsync(NewMultipartUploadPutArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + var uploadResponse = new NewMultipartUploadResponse(response.StatusCode, response.Content); + return uploadResponse.UploadId; + } + + /// + /// Start a new multi-part copy upload request + /// + /// + /// NewMultipartUploadCopyArgs arguments object encapsulating bucket name, object name, Headers, SSE + /// Headers + /// + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// For encrypted copy operation, Access is denied if the key is wrong + private async Task NewMultipartUploadAsync(NewMultipartUploadCopyArgs args, + CancellationToken cancellationToken = default) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + var uploadResponse = new NewMultipartUploadResponse(response.StatusCode, response.Content); + return uploadResponse.UploadId; + } + + /// + /// Create the copy request, execute it and return the copy result. + /// + /// CopyObjectRequestArgs Arguments Object encapsulating + /// Optional cancellation token to cancel the operation + private async Task CopyObjectRequestAsync(CopyObjectRequestArgs args, CancellationToken cancellationToken) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + var copyObjectResponse = + new CopyObjectResponse(response.StatusCode, response.Content, args.CopyOperationObjectType); + return copyObjectResponse.CopyPartRequestResult; + } + + /// + /// Internal method to complete multi part upload of object to server. + /// + /// CompleteMultipartUploadArgs Arguments object with bucket name, object name, upload id, Etags + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// For encrypted copy operation, Access is denied if the key is wrong + private async Task CompleteMultipartUploadAsync(CompleteMultipartUploadArgs args, + CancellationToken cancellationToken) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + return new PutObjectResponse(response.StatusCode, response.Content, response.Headers, -1, + args.ObjectName); + } + + /// + /// Advances in the stream upto currentPartSize or End of Stream + /// + /// + /// + /// bytes read in a byte array + internal async Task> ReadFullAsync(Stream data, int currentPartSize) + { + Memory result = new byte[currentPartSize]; + var totalRead = 0; + while (totalRead < currentPartSize) + { + Memory curData = new byte[currentPartSize - totalRead]; + var curRead = await data.ReadAsync(curData[..(currentPartSize - totalRead)]).ConfigureAwait(false); + if (curRead == 0) break; + for (var i = 0; i < curRead; i++) + curData.Slice(i, 1).CopyTo(result[(totalRead + i)..]); + totalRead += curRead; + } + + if (totalRead == 0) return null; + + if (totalRead == currentPartSize) return result; + + Memory truncatedResult = new byte[totalRead]; + for (var i = 0; i < totalRead; i++) + result.Slice(i, 1).CopyTo(truncatedResult[i..]); + return truncatedResult; + } +} diff --git a/LibExternal/Minio/BucketRegionCache.cs b/LibExternal/Minio/BucketRegionCache.cs new file mode 100644 index 0000000..de5aaf8 --- /dev/null +++ b/LibExternal/Minio/BucketRegionCache.cs @@ -0,0 +1,128 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Collections.Concurrent; +using System.Net; +using System.Xml.Linq; +using Minio.Helper; + +namespace Minio; + +/// +/// A singleton bucket/region cache map. +/// +public sealed class BucketRegionCache +{ + private static readonly Lazy lazy = new(() => new BucketRegionCache()); + + private readonly ConcurrentDictionary regionMap; + + private BucketRegionCache() + { + regionMap = new ConcurrentDictionary(StringComparer.Ordinal); + } + + public static BucketRegionCache Instance => lazy.Value; + + /// + /// Returns AWS region for given bucket name. + /// + /// + /// + public string Region(string bucketName) + { + _ = regionMap.TryGetValue(bucketName, out var value); + return value ?? "us-east-1"; + } + + /// + /// Adds bucket name and its region to BucketRegionCache. + /// + /// + /// + public void Add(string bucketName, string region) + { + _ = regionMap.TryAdd(bucketName, region); + } + + /// + /// Removes region cache of the bucket if any. + /// + /// + public void Remove(string bucketName) + { + _ = regionMap.TryRemove(bucketName, out _); + } + + /// + /// Returns true if given bucket name is in the map else false. + /// + /// + /// + public bool Exists(string bucketName) + { + _ = regionMap.TryGetValue(bucketName, out var value); + return !string.Equals(value, null, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Updates Region cache for given bucket. + /// + /// + /// + internal static async Task Update(IMinioClient client, string bucketName) + { + string region = null; + + if (!string.Equals(bucketName, null, StringComparison.OrdinalIgnoreCase) && client.Config.AccessKey is not null + && client.Config.SecretKey is not null && !Instance.Exists(bucketName)) + { + string location = null; + var path = Utils.UrlEncode(bucketName); + // Initialize client + var requestUrl = RequestUtil.MakeTargetURL(client.Config.BaseUrl, client.Config.Secure); + + var requestBuilder = new HttpRequestMessageBuilder(HttpMethod.Get, requestUrl, path); + requestBuilder.AddQueryParameter("location", ""); + using var response = + await client.ExecuteTaskAsync(client.ResponseErrorHandlers, requestBuilder).ConfigureAwait(false); + + if (response is not null && HttpStatusCode.OK.Equals(response.StatusCode)) + { + var root = XDocument.Parse(response.Content); + location = root.Root.Value; + } + + if (string.IsNullOrEmpty(location)) + { + region = "us-east-1"; + } + else + { + // eu-west-1 can be sometimes 'EU'. + if (string.Equals(location, "EU", StringComparison.OrdinalIgnoreCase)) + region = "eu-west-1"; + else + region = location; + } + + // Add the new location. + Instance.Add(bucketName, region); + } + + return region; + } +} diff --git a/LibExternal/Minio/Credentials/AWSEnvironmentProvider.cs b/LibExternal/Minio/Credentials/AWSEnvironmentProvider.cs new file mode 100644 index 0000000..f28e470 --- /dev/null +++ b/LibExternal/Minio/Credentials/AWSEnvironmentProvider.cs @@ -0,0 +1,55 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.DataModel; + +namespace Minio.Credentials; + +public class AWSEnvironmentProvider : IClientProvider +{ + internal string AccessKey + { + get + { + var accessKey = Environment.GetEnvironmentVariable("AWS_ACCESS_KEY_ID"); + if (string.IsNullOrWhiteSpace(accessKey)) accessKey = Environment.GetEnvironmentVariable("AWS_ACCESS_KEY"); + return accessKey; + } + } + + internal string SecretKey + { + get + { + var secretKey = Environment.GetEnvironmentVariable("AWS_SECRET_ACCESS_KEY"); + if (string.IsNullOrWhiteSpace(secretKey)) secretKey = Environment.GetEnvironmentVariable("AWS_SECRET_KEY"); + return secretKey; + } + } + + internal string SessionToken => Environment.GetEnvironmentVariable("AWS_SESSION_TOKEN"); + + public AccessCredentials GetCredentials() + { + return new AccessCredentials(AccessKey, SecretKey, SessionToken, default); + } + + public ValueTask GetCredentialsAsync() + { + return new ValueTask(GetCredentials()); + } +} diff --git a/LibExternal/Minio/Credentials/AssumeRoleBaseProvider.cs b/LibExternal/Minio/Credentials/AssumeRoleBaseProvider.cs new file mode 100644 index 0000000..febf8e1 --- /dev/null +++ b/LibExternal/Minio/Credentials/AssumeRoleBaseProvider.cs @@ -0,0 +1,150 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Globalization; +using System.Net; +using System.Text; +using Minio.DataModel; +using Minio.DataModel.Result; +using Minio.Handlers; +using Minio.Helper; + +namespace Minio.Credentials; + +// Assume-role credential provider +public abstract class AssumeRoleBaseProvider : IClientProvider + where T : AssumeRoleBaseProvider +{ + internal readonly IEnumerable NoErrorHandlers = + Enumerable.Empty(); + + protected AssumeRoleBaseProvider(IMinioClient client) + { + Client = client; + } + + protected AssumeRoleBaseProvider() + { + Client = null; + } + + internal AccessCredentials Credentials { get; set; } + internal IMinioClient Client { get; set; } + internal string Action { get; set; } + internal uint? DurationInSeconds { get; set; } + internal string Region { get; set; } + internal string RoleSessionName { get; set; } + internal string Policy { get; set; } + internal string RoleARN { get; set; } + internal string ExternalID { get; set; } + + public virtual async ValueTask GetCredentialsAsync() + { + if (Credentials?.AreExpired() == false) return Credentials; + + var requestBuilder = await BuildRequest().ConfigureAwait(false); + if (Client is not null) + { + ResponseResult responseMessage = null; + try + { + responseMessage = await Client.ExecuteTaskAsync(NoErrorHandlers, requestBuilder).ConfigureAwait(false); + } + finally + { + responseMessage?.Dispose(); + } + } + + return null; + } + + public virtual AccessCredentials GetCredentials() + { + throw new InvalidOperationException("Please use the GetCredentialsAsync method."); + } + + public T WithDurationInSeconds(uint? durationInSeconds) + { + DurationInSeconds = durationInSeconds; + return (T)this; + } + + public T WithRegion(string region) + { + Region = !string.IsNullOrWhiteSpace(region) ? region : ""; + return (T)this; + } + + public T WithRoleARN(string roleArn) + { + RoleARN = roleArn; + return (T)this; + } + + public T WithPolicy(string policy) + { + Policy = policy; + return (T)this; + } + + public T WithRoleSessionName(string sessionName) + { + RoleSessionName = sessionName; + return (T)this; + } + + public T WithExternalID(string externalId) + { + if (string.IsNullOrWhiteSpace(externalId)) + throw new ArgumentNullException(nameof(externalId), "The External ID cannot be null or empty."); + if (externalId.Length < 2 || externalId.Length > 1224) + throw new ArgumentOutOfRangeException(nameof(externalId), + "The External Id needs to be between 2 to 1224 characters in length"); + ExternalID = externalId; + return (T)this; + } + + public T WithRoleAction(string action) + { + Action = action; + return (T)this; + } + + internal virtual async Task BuildRequest() + { + if (Client is null) throw new InvalidOperationException("MinioClient is not set in AssumeRoleBaseProvider"); + var reqBuilder = await Client.CreateRequest(HttpMethod.Post).ConfigureAwait(false); + reqBuilder.AddQueryParameter("Action", Action); + reqBuilder.AddQueryParameter("Version", "2011-06-15"); + if (!string.IsNullOrWhiteSpace(Policy)) reqBuilder.AddQueryParameter("Policy", Policy); + if (!string.IsNullOrWhiteSpace(RoleARN)) reqBuilder.AddQueryParameter("RoleArn", RoleARN); + if (!string.IsNullOrWhiteSpace(RoleSessionName)) reqBuilder.AddQueryParameter("RoleSessionName", RoleARN); + + return reqBuilder; + } + + internal virtual AccessCredentials ParseResponse(HttpResponseMessage response) + { + var content = Convert.ToString(response.Content, CultureInfo.InvariantCulture); + if (string.IsNullOrEmpty(content) || !HttpStatusCode.OK.Equals(response.StatusCode)) + throw new ArgumentNullException(nameof(response), "Unable to generate credentials. Response error."); + + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(content).AsMemory().ToArray()); + return Utils.DeserializeXml(stream); + } +} diff --git a/LibExternal/Minio/Credentials/AssumeRoleProvider.cs b/LibExternal/Minio/Credentials/AssumeRoleProvider.cs new file mode 100644 index 0000000..c11ac53 --- /dev/null +++ b/LibExternal/Minio/Credentials/AssumeRoleProvider.cs @@ -0,0 +1,118 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Text; +using Minio.DataModel; +using Minio.DataModel.Result; +using Minio.Exceptions; +using Minio.Helper; + +namespace Minio.Credentials; + +public class AssumeRoleProvider : AssumeRoleBaseProvider +{ + private readonly string assumeRole = "AssumeRole"; + private readonly uint defaultDurationInSeconds = 3600; + + public AssumeRoleProvider() + { + } + + public AssumeRoleProvider(IMinioClient client) : base(client) + { + } + + internal string STSEndPoint { get; set; } + internal string Url { get; set; } + + public AssumeRoleProvider WithSTSEndpoint(string endpoint) + { + if (string.IsNullOrWhiteSpace(endpoint)) + throw new ArgumentNullException(nameof(endpoint), "The STS endpoint cannot be null or empty."); + + STSEndPoint = endpoint; + var stsUri = Utils.GetBaseUrl(endpoint); + if ((string.Equals(stsUri.Scheme, "http", StringComparison.OrdinalIgnoreCase) && stsUri.Port == 80) || + (string.Equals(stsUri.Scheme, "https", StringComparison.OrdinalIgnoreCase) && stsUri.Port == 443) || + stsUri.Port <= 0) + Url = stsUri.Scheme + "://" + stsUri.Authority; + else if (stsUri.Port > 0) Url = stsUri.Scheme + "://" + stsUri.Host + ":" + stsUri.Port; + + Url = stsUri.Authority; + + return this; + } + + public override async ValueTask GetCredentialsAsync() + { + if (Credentials?.AreExpired() == false) return Credentials; + + var requestBuilder = await BuildRequest().ConfigureAwait(false); + if (Client is not null) + { + ResponseResult responseResult = null; + try + { + responseResult = await Client.ExecuteTaskAsync(NoErrorHandlers, requestBuilder, true) + .ConfigureAwait(false); + + AssumeRoleResponse assumeRoleResp = null; + if (responseResult.Response.IsSuccessStatusCode) + { + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(responseResult.Content).AsMemory().ToArray()); + assumeRoleResp = Utils.DeserializeXml(stream); + } + + if (Credentials is null && + assumeRoleResp?.AssumeRole is not null) + Credentials = assumeRoleResp.AssumeRole.Credentials; + + return Credentials; + } + finally + { + responseResult?.Dispose(); + } + } + + throw new InternalClientException("Client should have been assigned for the operation to continue."); + } + + internal override async Task BuildRequest() + { + Action = assumeRole; + if (DurationInSeconds is null || DurationInSeconds.Value == 0) + DurationInSeconds = defaultDurationInSeconds; + + var requestMessageBuilder = await Client.CreateRequest(HttpMethod.Post).ConfigureAwait(false); + + using var formContent = new FormUrlEncodedContent(new[] + { + new KeyValuePair("Action", "AssumeRole"), + new KeyValuePair("DurationSeconds", DurationInSeconds.ToString()), + new KeyValuePair("Version", "2011-06-15") + }); + ReadOnlyMemory byteArrContent = await formContent.ReadAsByteArrayAsync().ConfigureAwait(false); + requestMessageBuilder.SetBody(byteArrContent); + requestMessageBuilder.AddOrUpdateHeaderParameter("Content-Type", + "application/x-www-form-urlencoded; charset=utf-8"); + requestMessageBuilder.AddOrUpdateHeaderParameter("Accept-Encoding", "identity"); + await Task.Yield(); + + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/Credentials/AssumeRoleResponse.cs b/LibExternal/Minio/Credentials/AssumeRoleResponse.cs new file mode 100644 index 0000000..0773dde --- /dev/null +++ b/LibExternal/Minio/Credentials/AssumeRoleResponse.cs @@ -0,0 +1,46 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml; +using System.Xml.Serialization; + +namespace Minio.Credentials; + +[Serializable] +[XmlRoot(ElementName = "AssumeRoleResponse", Namespace = "https://sts.amazonaws.com/doc/2011-06-15/")] +public class AssumeRoleResponse +{ + [XmlElement(ElementName = "AssumeRoleResult")] + public AssumeRoleResult AssumeRole { get; set; } + + public string ToXML() + { + var settings = new XmlWriterSettings { OmitXmlDeclaration = true }; + using var ms = new MemoryStream(); + using var xmlWriter = XmlWriter.Create(ms, settings); + var names = new XmlSerializerNamespaces(); + names.Add(string.Empty, "https://sts.amazonaws.com/doc/2011-06-15/"); + + var cs = new XmlSerializer(typeof(CertificateResponse)); + cs.Serialize(xmlWriter, this, names); + + ms.Flush(); + _ = ms.Seek(0, SeekOrigin.Begin); + using var streamReader = new StreamReader(ms); + return streamReader.ReadToEnd(); + } +} diff --git a/LibExternal/Minio/Credentials/AssumeRoleResult.cs b/LibExternal/Minio/Credentials/AssumeRoleResult.cs new file mode 100644 index 0000000..9fea2d2 --- /dev/null +++ b/LibExternal/Minio/Credentials/AssumeRoleResult.cs @@ -0,0 +1,29 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; +using Minio.DataModel; + +namespace Minio.Credentials; + +[Serializable] +[XmlRoot(ElementName = "AssumeRoleResult")] +public class AssumeRoleResult +{ + [XmlElement(ElementName = "Credentials")] + public AccessCredentials Credentials { get; set; } +} diff --git a/LibExternal/Minio/Credentials/CertificateIdentityProvider.cs b/LibExternal/Minio/Credentials/CertificateIdentityProvider.cs new file mode 100644 index 0000000..d3f61eb --- /dev/null +++ b/LibExternal/Minio/Credentials/CertificateIdentityProvider.cs @@ -0,0 +1,136 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2022 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +using System.Globalization; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Web; +using Minio.DataModel; +using Minio.Exceptions; +using Minio.Helper; +#if (NET472_OR_GREATER || NET6_0_OR_GREATER) +using System.Security.Authentication; +#else +using System.Net; +#endif + +/* + * Certificate Identity Credential provider. + * This is a MinIO Extension to AssumeRole STS APIs on + * AWS, purely based on client certificates mTLS authentication. + */ + +namespace Minio.Credentials; + +public class CertificateIdentityProvider : IClientProvider +{ + private readonly int defaultDurationInSeconds = 3600; + + public CertificateIdentityProvider() + { + DurationInSeconds = defaultDurationInSeconds; + } + + internal string StsEndpoint { get; set; } + internal int DurationInSeconds { get; set; } + internal X509Certificate2 ClientCertificate { get; set; } + internal Uri PostEndpoint { get; set; } + internal HttpClient HttpClient { get; set; } + internal AccessCredentials Credentials { get; set; } + + public AccessCredentials GetCredentials() + { + return GetCredentialsAsync().AsTask().ConfigureAwait(false).GetAwaiter().GetResult(); + } + + public async ValueTask GetCredentialsAsync() + { + if (Credentials?.AreExpired() == false) + return Credentials; + + if (HttpClient is null) + throw new InvalidOperationException(nameof(HttpClient) + " cannot be null or empty"); + + if (ClientCertificate is null) + throw new InvalidOperationException(nameof(ClientCertificate) + " cannot be null or empty"); + + using var response = await HttpClient.PostAsync(PostEndpoint, null).ConfigureAwait(false); + + var certResponse = new CertificateResponse(); + if (response.IsSuccessStatusCode) + { + var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(content).AsMemory().ToArray()); + certResponse = Utils.DeserializeXml(stream); + } + + if (Credentials is null && certResponse?.Cr is not null) + Credentials = certResponse.Cr.Credentials; + + return Credentials; + } + + public CertificateIdentityProvider WithStsEndpoint(string stsEndpoint) + { + if (string.IsNullOrEmpty(stsEndpoint)) + throw new InvalidEndpointException("Missing mandatory argument: stsEndpoint"); + if (!stsEndpoint.StartsWith("https", StringComparison.OrdinalIgnoreCase)) + throw new InvalidEndpointException($"stsEndpoint {stsEndpoint} is invalid." + " The scheme must be https"); + + StsEndpoint = stsEndpoint; + return this; + } + + public CertificateIdentityProvider WithHttpClient(HttpClient httpClient = null) + { + HttpClient = httpClient; + return this; + } + + public CertificateIdentityProvider WithCertificate(X509Certificate2 cert = null) + { + ClientCertificate = cert; + return this; + } + + public CertificateIdentityProvider Build() + { + if (string.IsNullOrEmpty(DurationInSeconds.ToString(CultureInfo.InvariantCulture))) + DurationInSeconds = defaultDurationInSeconds; + + var builder = new UriBuilder(StsEndpoint); + var query = HttpUtility.ParseQueryString(builder.Query); + query["Action"] = "AssumeRoleWithCertificate"; + query["Version"] = "2011-06-15"; + query["DurationInSeconds"] = DurationInSeconds.ToString(CultureInfo.InvariantCulture); + builder.Query = query.ToString(); + PostEndpoint = builder.Uri; + + var handler = new HttpClientHandler { ClientCertificateOptions = ClientCertificateOption.Manual }; +#if (NET472_OR_GREATER || NET6_0_OR_GREATER) + handler.SslProtocols = SslProtocols.Tls12; +#else + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11; +#endif + _ = handler.ClientCertificates.Add(ClientCertificate); + HttpClient ??= new HttpClient(handler) { BaseAddress = new Uri(StsEndpoint) }; + + Credentials = GetCredentials(); + return this; + } +} diff --git a/LibExternal/Minio/Credentials/CertificateResponse.cs b/LibExternal/Minio/Credentials/CertificateResponse.cs new file mode 100644 index 0000000..d2681c9 --- /dev/null +++ b/LibExternal/Minio/Credentials/CertificateResponse.cs @@ -0,0 +1,52 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2022 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml; +using System.Xml.Serialization; + +/* + * Certificate Identity Credential provider. + * This is a MinIO Extension to AssumeRole STS APIs on + * AWS, purely based on client certificates mTLS authentication. + */ + +namespace Minio.Credentials; + +[Serializable] +[XmlRoot(ElementName = "AssumeRoleWithCertificateResponse", Namespace = "https://sts.amazonaws.com/doc/2011-06-15/")] +public class CertificateResponse +{ + [XmlElement(ElementName = "AssumeRoleWithCertificateResult")] + public CertificateResult Cr { get; set; } + + public string ToXML() + { + var settings = new XmlWriterSettings { OmitXmlDeclaration = true }; + using var ms = new MemoryStream(); + using var xmlWriter = XmlWriter.Create(ms, settings); + var names = new XmlSerializerNamespaces(); + names.Add(string.Empty, "https://sts.amazonaws.com/doc/2011-06-15/"); + + var cs = new XmlSerializer(typeof(CertificateResponse)); + cs.Serialize(xmlWriter, this, names); + + ms.Flush(); + _ = ms.Seek(0, SeekOrigin.Begin); + using var streamReader = new StreamReader(ms); + return streamReader.ReadToEnd(); + } +} diff --git a/LibExternal/Minio/Credentials/CertificateResult.cs b/LibExternal/Minio/Credentials/CertificateResult.cs new file mode 100644 index 0000000..241ea45 --- /dev/null +++ b/LibExternal/Minio/Credentials/CertificateResult.cs @@ -0,0 +1,35 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2022 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; +using Minio.DataModel; + +/* + * Certificate Identity Credential provider. + * This is a MinIO Extension to AssumeRole STS APIs on + * AWS, purely based on client certificates mTLS authentication. + */ + +namespace Minio.Credentials; + +[Serializable] +[XmlRoot(ElementName = "AssumeRoleWithCertificateResult")] +public class CertificateResult +{ + [XmlElement(ElementName = "Credentials")] + public AccessCredentials Credentials { get; set; } +} diff --git a/LibExternal/Minio/Credentials/ChainedProvider.cs b/LibExternal/Minio/Credentials/ChainedProvider.cs new file mode 100644 index 0000000..5dba372 --- /dev/null +++ b/LibExternal/Minio/Credentials/ChainedProvider.cs @@ -0,0 +1,72 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.DataModel; + +namespace Minio.Credentials; + +public class ChainedProvider : IClientProvider +{ + public ChainedProvider() + { + Providers = new List(); + } + + internal List Providers { get; set; } + internal IClientProvider CurrentProvider { get; set; } + internal AccessCredentials Credentials { get; set; } + + public AccessCredentials GetCredentials() + { + if (Credentials?.AreExpired() == false) return Credentials; + if (CurrentProvider is not null && !Credentials.AreExpired()) + { + Credentials = CurrentProvider.GetCredentials(); + return CurrentProvider.GetCredentials(); + } + + foreach (var provider in Providers) + { + var credentials = provider.GetCredentials(); + if (credentials?.AreExpired() == false) + { + CurrentProvider = provider; + Credentials = credentials; + return credentials; + } + } + + throw new InvalidOperationException("None of the assigned providers were able to provide valid credentials."); + } + + public ValueTask GetCredentialsAsync() + { + return new ValueTask(GetCredentials()); + } + + public ChainedProvider AddProvider(IClientProvider provider) + { + Providers.Add(provider); + return this; + } + + public ChainedProvider AddProviders(IClientProvider[] providers) + { + Providers.AddRange(providers.ToList()); + return this; + } +} diff --git a/LibExternal/Minio/Credentials/ECSCredentials.cs b/LibExternal/Minio/Credentials/ECSCredentials.cs new file mode 100644 index 0000000..e4b84c7 --- /dev/null +++ b/LibExternal/Minio/Credentials/ECSCredentials.cs @@ -0,0 +1,47 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Text.Json.Serialization; +using Minio.DataModel; +using Minio.Helper; + +namespace Minio.Credentials; + +public class ECSCredentials +{ + [JsonPropertyName("AccessKeyId")] public string AccessKeyId { get; set; } + + [JsonPropertyName("SecretAccessKey")] public string SecretAccessKey { get; set; } + + [JsonPropertyName("Token")] public string SessionToken { get; set; } + + [JsonPropertyName("Expiration")] public string ExpirationDate { get; set; } + + [JsonPropertyName("Code")] public string Code { get; set; } + + [JsonPropertyName("Message")] public string Message { get; set; } + + [JsonPropertyName("Type")] public string Type { get; set; } + + [JsonPropertyName("LastUpdated")] public string LastUpdated { get; set; } + + public AccessCredentials GetAccessCredentials() + { + return new AccessCredentials(AccessKeyId, SecretAccessKey, SessionToken, + Utils.From8601String(ExpirationDate)); + } +} diff --git a/LibExternal/Minio/Credentials/IAMAWSProvider.cs b/LibExternal/Minio/Credentials/IAMAWSProvider.cs new file mode 100644 index 0000000..a80bf9e --- /dev/null +++ b/LibExternal/Minio/Credentials/IAMAWSProvider.cs @@ -0,0 +1,255 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Net; +using System.Text.Json; +using Minio.DataModel; +using Minio.Exceptions; +using Minio.Handlers; +using Minio.Helper; + +/* + * IAM roles for Amazon EC2 + * http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html + * The Credential provider for attaching an IAM rule. + */ + +namespace Minio.Credentials; + +public class IAMAWSProvider : IClientProvider +{ + public IAMAWSProvider() + { + Client = null; + } + + public IAMAWSProvider(string endpoint, IMinioClient client) + { + if (!string.IsNullOrWhiteSpace(endpoint)) + { + CustomEndPoint = new Uri(endpoint); + if (string.IsNullOrWhiteSpace(CustomEndPoint.Authority)) + throw new ArgumentNullException(nameof(endpoint), + "Endpoint field " + nameof(CustomEndPoint) + " is invalid."); + } + + Client = client ?? throw new ArgumentNullException(nameof(client)); + + CustomEndPoint = new Uri(endpoint); + } + + internal Uri CustomEndPoint { get; set; } + internal AccessCredentials Credentials { get; set; } + internal IMinioClient Client { get; set; } + + public AccessCredentials GetCredentials() + { + Validate(); + var url = CustomEndPoint; + if (CustomEndPoint is null) + { + var region = Environment.GetEnvironmentVariable("AWS_REGION"); + if (string.IsNullOrWhiteSpace(region)) + url = RequestUtil.MakeTargetURL("sts.amazonaws.com", true); + else + url = RequestUtil.MakeTargetURL("sts." + region + ".amazonaws.com", true); + } + + var provider = new WebIdentityProvider() + .WithSTSEndpoint(url) + .WithRoleAction("AssumeRoleWithWebIdentity") + .WithDurationInSeconds(null) + .WithPolicy(null) + .WithRoleARN(Environment.GetEnvironmentVariable("AWS_ROLE_ARN")) + .WithRoleSessionName(Environment.GetEnvironmentVariable("AWS_ROLE_SESSION_NAME")); + Credentials = provider.GetCredentials(); + return Credentials; + } + + public async ValueTask GetCredentialsAsync() + { + if (Credentials?.AreExpired() == false) return Credentials; + + var url = CustomEndPoint; + var awsTokenFile = Environment.GetEnvironmentVariable("AWS_WEB_IDENTITY_TOKEN_FILE"); + if (!string.IsNullOrWhiteSpace(awsTokenFile)) + { + Credentials = GetAccessCredentials(awsTokenFile); + return Credentials; + } + + var containerRelativeUri = Environment.GetEnvironmentVariable("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"); + var containerFullUri = Environment.GetEnvironmentVariable("AWS_CONTAINER_CREDENTIALS_FULL_URI"); + var isURLEmpty = url is null; + if (!string.IsNullOrWhiteSpace(containerRelativeUri) && isURLEmpty) + { + url = RequestUtil.MakeTargetURL("169.254.170.2" + "/" + containerRelativeUri, false); + } + else if (!string.IsNullOrWhiteSpace(containerFullUri) && isURLEmpty) + { + var fullUri = new Uri(containerFullUri); + url = RequestUtil.MakeTargetURL(fullUri.AbsolutePath, + string.Equals(fullUri.Scheme, "https", StringComparison.OrdinalIgnoreCase)); + } + else + { + url = await GetIamRoleNamedURL().ConfigureAwait(false); + } + + Credentials = await GetAccessCredentials(url).ConfigureAwait(false); + return Credentials; + } + + internal AccessCredentials GetAccessCredentials(string tokenFile) + { + Validate(); + var url = CustomEndPoint; + if (url is null || string.IsNullOrWhiteSpace(url.Authority)) + { + var region = Environment.GetEnvironmentVariable("AWS_REGION"); + var urlStr = region is null ? "https://sts.amazonaws.com" : "https://sts." + region + ".amazonaws.com"; + url = new Uri(urlStr); + } + + var provider = new WebIdentityProvider() + .WithJWTSupplier(() => + { + var tokenContents = File.ReadAllText(tokenFile); + return new JsonWebToken(tokenContents, 0); + }) + .WithSTSEndpoint(url) + .WithDurationInSeconds(null) + .WithPolicy(null) + .WithRoleARN(Environment.GetEnvironmentVariable("AWS_ROLE_ARN")) + .WithRoleSessionName(Environment.GetEnvironmentVariable("AWS_ROLE_SESSION_NAME")); + Credentials = provider.GetCredentials(); + return Credentials; + } + + public async Task GetAccessCredentials(Uri url) + { + if (url is null) + throw new ArgumentNullException(nameof(url)); + + Validate(); + using var request = new HttpRequestMessage(HttpMethod.Get, url.ToString()); + + var requestBuilder = new HttpRequestMessageBuilder(HttpMethod.Get, url); + requestBuilder.AddQueryParameter("location", ""); + + using var response = + await Client.ExecuteTaskAsync(Enumerable.Empty(), requestBuilder) + .ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(response.Content) || + !HttpStatusCode.OK.Equals(response.StatusCode)) + throw new CredentialsProviderException("IAMAWSProvider", + "Credential Get operation failed with HTTP Status code: " + response.StatusCode); + /* +JsonConvert.DefaultSettings = () => new JsonSerializerSettings +{ + MissingMemberHandling = MissingMemberHandling.Error, + ContractResolver = new CamelCasePropertyNamesContractResolver(), + Error = null +};*/ + + var credentials = JsonSerializer.Deserialize(response.Content); + if (credentials.Code?.Equals("success", StringComparison.OrdinalIgnoreCase) == false) + throw new CredentialsProviderException("IAMAWSProvider", + "Credential Get operation failed with code: " + credentials.Code + " and message " + + credentials.Message); + + Credentials = credentials.GetAccessCredentials(); + return Credentials; + } + + public async Task GetIamRoleNameAsync(Uri url) + { + Validate(); + var requestBuilder = new HttpRequestMessageBuilder(HttpMethod.Get, url); + requestBuilder.AddQueryParameter("location", ""); + + using var response = + await Client.ExecuteTaskAsync(Enumerable.Empty(), requestBuilder) + .ConfigureAwait(false); + + if (string.IsNullOrWhiteSpace(response.Content) || + !HttpStatusCode.OK.Equals(response.StatusCode)) + throw new CredentialsProviderException("IAMAWSProvider", + "Credential Get operation failed with HTTP Status code: " + response.StatusCode); + + var roleNames = response.Content.Split('\n'); + if (roleNames.Length <= 0) + throw new CredentialsProviderException("IAMAWSProvider", + "No IAM roles are attached to AWS service at " + url); + + var index = 0; + foreach (var item in roleNames) roleNames[index++] = item.Trim(); + return roleNames[0]; + } + + public async Task GetIamRoleNamedURL() + { + Validate(); + var url = CustomEndPoint; + string newUrlStr; + if (url is null || string.IsNullOrWhiteSpace(url.Authority)) + { + url = new Uri("http://169.254.169.254/latest/meta-data/iam/security-credentials/"); + newUrlStr = "http://169.254.169.254/latest/meta-data/iam/security-credentials/"; + } + else + { + var urlStr = url.Scheme + "://" + url.Authority + "/latest/meta-data/iam/security-credentials/"; + url = new Uri(urlStr); + newUrlStr = urlStr; + } + + var roleName = await GetIamRoleNameAsync(url).ConfigureAwait(false); + newUrlStr += roleName; + return new Uri(newUrlStr); + } + + public IAMAWSProvider WithMinioClient(IMinioClient minio) + { + Client = minio; + if (Credentials is null || + string.IsNullOrWhiteSpace(Credentials.AccessKey) || string.IsNullOrWhiteSpace(Credentials.SecretKey)) + Credentials = GetCredentialsAsync().AsTask().GetAwaiter().GetResult(); + + return this; + } + + public IAMAWSProvider WithEndpoint(string endpoint) + { + if (string.IsNullOrEmpty(endpoint)) + throw new ArgumentException($"'{nameof(endpoint)}' cannot be null or empty.", nameof(endpoint)); + + if (endpoint.Contains("https", StringComparison.OrdinalIgnoreCase) || + endpoint.Contains("http", StringComparison.OrdinalIgnoreCase)) + CustomEndPoint = new Uri(endpoint); + else + CustomEndPoint = RequestUtil.MakeTargetURL(endpoint, true); + return this; + } + + public void Validate() + { + if (Client is null) + throw new InvalidOperationException(nameof(Client) + + " should be assigned for the operation to continue."); + } +} diff --git a/LibExternal/Minio/Credentials/IClientProvider.cs b/LibExternal/Minio/Credentials/IClientProvider.cs new file mode 100644 index 0000000..fb6258a --- /dev/null +++ b/LibExternal/Minio/Credentials/IClientProvider.cs @@ -0,0 +1,26 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.DataModel; + +namespace Minio.Credentials; + +public interface IClientProvider +{ + AccessCredentials GetCredentials(); + ValueTask GetCredentialsAsync(); +} diff --git a/LibExternal/Minio/Credentials/JsonWebToken.cs b/LibExternal/Minio/Credentials/JsonWebToken.cs new file mode 100644 index 0000000..abe4eba --- /dev/null +++ b/LibExternal/Minio/Credentials/JsonWebToken.cs @@ -0,0 +1,33 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Text.Json.Serialization; + +namespace Minio.Credentials; + +public class JsonWebToken +{ + public JsonWebToken(string token, uint expiry) + { + AccessToken = token; + Expiry = expiry; + } + + [JsonPropertyName("access_token")] public string AccessToken { get; set; } + + [JsonPropertyName("expires_in")] public uint Expiry { get; set; } +} diff --git a/LibExternal/Minio/Credentials/MinioEnvironmentProvider.cs b/LibExternal/Minio/Credentials/MinioEnvironmentProvider.cs new file mode 100644 index 0000000..6092464 --- /dev/null +++ b/LibExternal/Minio/Credentials/MinioEnvironmentProvider.cs @@ -0,0 +1,42 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.DataModel; + +namespace Minio.Credentials; + +public class MinioEnvironmentProvider : IClientProvider +{ + public AccessCredentials GetCredentials() + { + var accessKey = Environment.GetEnvironmentVariable("MINIO_ROOT_USER"); + var secretKey = Environment.GetEnvironmentVariable("MINIO_ROOT_PASSWORD"); + + if (string.IsNullOrEmpty(accessKey)) + accessKey = Environment.GetEnvironmentVariable("MINIO_ACCESS_KEY"); + + if (string.IsNullOrEmpty(secretKey)) + secretKey = Environment.GetEnvironmentVariable("MINIO_SECRET_KEY"); + + return new AccessCredentials(accessKey, secretKey, null, default); + } + + public ValueTask GetCredentialsAsync() + { + return new ValueTask(GetCredentials()); + } +} diff --git a/LibExternal/Minio/Credentials/WebIdentityClientGrantsProvider.cs b/LibExternal/Minio/Credentials/WebIdentityClientGrantsProvider.cs new file mode 100644 index 0000000..da061e3 --- /dev/null +++ b/LibExternal/Minio/Credentials/WebIdentityClientGrantsProvider.cs @@ -0,0 +1,81 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Globalization; +using System.Net; +using System.Text; +using Minio.DataModel; +using Minio.Helper; + +namespace Minio.Credentials; + +public abstract class WebIdentityClientGrantsProvider : AssumeRoleBaseProvider + where T : WebIdentityClientGrantsProvider +{ + internal readonly uint MAX_DURATION_SECONDS = (uint)new TimeSpan(7, 0, 0, 0).TotalSeconds; + internal readonly uint MIN_DURATION_SECONDS = 15; + + internal Uri STSEndpoint { get; set; } + internal Func JWTSupplier { get; set; } + + internal uint GetDurationInSeconds(uint expiry) + { + if (DurationInSeconds is not null && DurationInSeconds.Value > 0) expiry = DurationInSeconds.Value; + if (expiry > MAX_DURATION_SECONDS) return MAX_DURATION_SECONDS; + return expiry < MIN_DURATION_SECONDS ? MIN_DURATION_SECONDS : expiry; + } + + internal T WithSTSEndpoint(Uri endpoint) + { + STSEndpoint = endpoint; + return (T)this; + } + + internal override async Task BuildRequest() + { + Validate(); + var jwt = JWTSupplier(); + var requestMessageBuilder = await base.BuildRequest().ConfigureAwait(false); + requestMessageBuilder = Utils.GetEmptyRestRequest(requestMessageBuilder); + requestMessageBuilder.AddQueryParameter("WebIdentityToken", jwt.AccessToken); + await Task.Yield(); + return requestMessageBuilder; + } + + internal override AccessCredentials ParseResponse(HttpResponseMessage response) + { + Validate(); + // Stream receiveStream = response.Content.ReadAsStreamAsync(); + // StreamReader readStream = new StreamReader (receiveStream, Encoding.UTF8); + // txtBlock.Text = readStream.ReadToEnd(); + var content = Convert.ToString(response.Content, CultureInfo.InvariantCulture); + if (string.IsNullOrWhiteSpace(content) || + !HttpStatusCode.OK.Equals(response.StatusCode)) + throw new ArgumentNullException(nameof(response), "Unable to get credentials. Response error."); + + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(content).AsMemory().ToArray()); + return Utils.DeserializeXml(stream); + } + + protected void Validate() + { + if (JWTSupplier is null) + throw new InvalidOperationException(nameof(JWTSupplier) + " JWT Token supplier cannot be null."); + if (STSEndpoint is null || string.IsNullOrWhiteSpace(STSEndpoint.AbsoluteUri)) + throw new InvalidOperationException(nameof(STSEndpoint) + " value is invalid."); + } +} diff --git a/LibExternal/Minio/Credentials/WebIdentityProvider.cs b/LibExternal/Minio/Credentials/WebIdentityProvider.cs new file mode 100644 index 0000000..dae827c --- /dev/null +++ b/LibExternal/Minio/Credentials/WebIdentityProvider.cs @@ -0,0 +1,72 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Globalization; +using System.Text; +using Minio.DataModel; +using Minio.Helper; + +/* + * Web Identity Credential provider + * https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html + */ + +namespace Minio.Credentials; + +public class WebIdentityProvider : WebIdentityClientGrantsProvider +{ + internal int ExpiryInSeconds { get; set; } + internal JsonWebToken CurrentJsonWebToken { get; set; } + + public override AccessCredentials GetCredentials() + { + Validate(); + return base.GetCredentials(); + } + + public override ValueTask GetCredentialsAsync() + { + Validate(); + return base.GetCredentialsAsync(); + } + + internal WebIdentityProvider WithJWTSupplier(Func f) + { + JWTSupplier = (Func)f.Clone(); + Validate(); + return this; + } + + internal override Task BuildRequest() + { + Validate(); + CurrentJsonWebToken = JWTSupplier(); + // RoleArn to be set already. + _ = WithRoleAction("AssumeRoleWithWebIdentity"); + _ = WithDurationInSeconds(GetDurationInSeconds(CurrentJsonWebToken.Expiry)); + RoleSessionName ??= Utils.To8601String(DateTime.Now); + return base.BuildRequest(); + } + + internal override AccessCredentials ParseResponse(HttpResponseMessage response) + { + Validate(); + var credentials = base.ParseResponse(response); + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(Convert.ToString(response.Content, CultureInfo.InvariantCulture)).AsMemory().ToArray()); + return Utils.DeserializeXml(stream); + } +} diff --git a/LibExternal/Minio/Credentials/WebIdentityResponse.cs b/LibExternal/Minio/Credentials/WebIdentityResponse.cs new file mode 100644 index 0000000..b7ddaf2 --- /dev/null +++ b/LibExternal/Minio/Credentials/WebIdentityResponse.cs @@ -0,0 +1,33 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; +using Minio.DataModel; + +/* + * Web Identity Credential provider + * https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html + */ + +namespace Minio.Credentials; + +[Serializable] +[XmlRoot(ElementName = "AssumeRoleWithWebIdentityResponse")] +public class WebIdentityResponse +{ + [XmlElement("Credentials")] public AccessCredentials Credentials { get; set; } +} diff --git a/LibExternal/Minio/DataModel/AccessCredentials.cs b/LibExternal/Minio/DataModel/AccessCredentials.cs new file mode 100644 index 0000000..ce4e68e --- /dev/null +++ b/LibExternal/Minio/DataModel/AccessCredentials.cs @@ -0,0 +1,73 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; +using Minio.Helper; + +namespace Minio.DataModel; + +[Serializable] +[XmlRoot(ElementName = "Credentials")] +public class AccessCredentials +{ + public AccessCredentials(string accessKey, string secretKey, + string sessionToken, DateTime expiration) + { + if (!string.IsNullOrEmpty(accessKey) || !string.IsNullOrEmpty(secretKey) || !string.IsNullOrEmpty(sessionToken)) + { + AccessKey = accessKey; + SecretKey = secretKey; + SessionToken = sessionToken; + Expiration = expiration.Equals(default) ? null : Utils.To8601String(expiration); + } + else + { + if (string.IsNullOrEmpty(accessKey)) + throw new ArgumentException($"'{nameof(accessKey)}' cannot be null or empty.", nameof(accessKey)); + + if (string.IsNullOrEmpty(secretKey)) + throw new ArgumentException($"'{nameof(secretKey)}' cannot be null or empty.", nameof(secretKey)); + + if (string.IsNullOrEmpty(sessionToken)) + throw new ArgumentException($"'{nameof(sessionToken)}' cannot be null or empty.", nameof(sessionToken)); + } + } + + public AccessCredentials() + { + } + + [XmlElement(ElementName = "AccessKeyId", IsNullable = true)] + public string AccessKey { get; set; } + + [XmlElement(ElementName = "SecretAccessKey", IsNullable = true)] + public string SecretKey { get; set; } + + [XmlElement(ElementName = "SessionToken", IsNullable = true)] + public string SessionToken { get; set; } + + // Needs to be stored in ISO8601 format from Datetime + [XmlElement(ElementName = "Expiration", IsNullable = true)] + public string Expiration { get; set; } + + public bool AreExpired() + { + if (string.IsNullOrWhiteSpace(Expiration)) return false; + var expiry = Utils.From8601String(Expiration); + return DateTime.Now.CompareTo(expiry) > 0; + } +} diff --git a/LibExternal/Minio/DataModel/Args/BucketArgs.cs b/LibExternal/Minio/DataModel/Args/BucketArgs.cs new file mode 100644 index 0000000..3170e6e --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/BucketArgs.cs @@ -0,0 +1,55 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.Helper; + +namespace Minio.DataModel.Args; + +public abstract class BucketArgs : RequestArgs + where T : BucketArgs +{ + protected const string BucketForceDeleteKey = "X-Minio-Force-Delete"; + + public bool IsBucketCreationRequest { get; set; } + + internal string BucketName { get; set; } + + internal IDictionary Headers { get; set; } = new Dictionary(StringComparer.Ordinal); + + public T WithBucket(string bucket) + { + BucketName = bucket; + return (T)this; + } + + public virtual T WithHeaders(IDictionary headers) + { + if (headers is null || headers.Count <= 0) return (T)this; + Headers ??= new Dictionary(StringComparer.Ordinal); + foreach (var key in headers.Keys) + { + _ = Headers.Remove(key); + Headers[key] = headers[key]; + } + + return (T)this; + } + + internal virtual void Validate() + { + Utils.ValidateBucketName(BucketName); + } +} diff --git a/LibExternal/Minio/DataModel/Args/BucketExistsArgs.cs b/LibExternal/Minio/DataModel/Args/BucketExistsArgs.cs new file mode 100644 index 0000000..99647a1 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/BucketExistsArgs.cs @@ -0,0 +1,25 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class BucketExistsArgs : BucketArgs +{ + public BucketExistsArgs() + { + RequestMethod = HttpMethod.Head; + } +} diff --git a/LibExternal/Minio/DataModel/Args/ClearObjectRetentionArgs.cs b/LibExternal/Minio/DataModel/Args/ClearObjectRetentionArgs.cs new file mode 100644 index 0000000..665bca6 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/ClearObjectRetentionArgs.cs @@ -0,0 +1,55 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Globalization; +using System.Text; +using System.Xml; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +public class ClearObjectRetentionArgs : ObjectVersionArgs +{ + public ClearObjectRetentionArgs() + { + RequestMethod = HttpMethod.Put; + } + + public static string EmptyRetentionConfigXML() + { + using var sw = new StringWriter(CultureInfo.InvariantCulture); + var settings = new XmlWriterSettings { OmitXmlDeclaration = true, Indent = true }; + using var xw = XmlWriter.Create(sw, settings); + xw.WriteStartElement("Retention"); + xw.WriteString(""); + xw.WriteFullEndElement(); + xw.Flush(); + return sw.ToString(); + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("retention", ""); + if (!string.IsNullOrEmpty(VersionId)) requestMessageBuilder.AddQueryParameter("versionId", VersionId); + // Required for Clear Object Retention. + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-bypass-governance-retention", "true"); + var body = EmptyRetentionConfigXML(); + requestMessageBuilder.AddXmlBody(body); + requestMessageBuilder.AddOrUpdateHeaderParameter("Content-Md5", + Utils.GetMD5SumStr(Encoding.UTF8.GetBytes(body))); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/CompleteMultipartUploadArgs.cs b/LibExternal/Minio/DataModel/Args/CompleteMultipartUploadArgs.cs new file mode 100644 index 0000000..68f8c74 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/CompleteMultipartUploadArgs.cs @@ -0,0 +1,86 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Text; +using System.Xml.Linq; + +namespace Minio.DataModel.Args; + +internal class CompleteMultipartUploadArgs : ObjectWriteArgs +{ + internal CompleteMultipartUploadArgs() + { + RequestMethod = HttpMethod.Post; + } + + internal CompleteMultipartUploadArgs(MultipartCopyUploadArgs args) + { + // destBucketName, destObjectName, metadata, sseHeaders + RequestMethod = HttpMethod.Post; + BucketName = args.BucketName; + ObjectName = args.ObjectName ?? args.SourceObject.ObjectName; + Headers = new Dictionary(StringComparer.Ordinal); + SSE = args.SSE; + SSE?.Marshal(args.Headers); + if (args.Headers?.Count > 0) + Headers = Headers.Concat(args.Headers).GroupBy(item => item.Key, StringComparer.Ordinal) + .ToDictionary(item => item.Key, item => item.First().Value, StringComparer.Ordinal); + } + + internal string UploadId { get; set; } + internal Dictionary ETags { get; set; } + + internal override void Validate() + { + base.Validate(); + if (string.IsNullOrWhiteSpace(UploadId)) + throw new InvalidOperationException(nameof(UploadId) + " cannot be empty."); + if (ETags is null || ETags.Count <= 0) + throw new InvalidOperationException(nameof(ETags) + " dictionary cannot be empty."); + } + + internal CompleteMultipartUploadArgs WithUploadId(string uploadId) + { + UploadId = uploadId; + return this; + } + + internal CompleteMultipartUploadArgs WithETags(IDictionary etags) + { + if (etags?.Count > 0) ETags = new Dictionary(etags); + return this; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("uploadId", $"{UploadId}"); + var parts = new List(); + + for (var i = 1; i <= ETags.Count; i++) + parts.Add(new XElement("Part", + new XElement("PartNumber", i), + new XElement("ETag", ETags[i]))); + + var completeMultipartUploadXml = new XElement("CompleteMultipartUpload", parts); + var bodyString = completeMultipartUploadXml.ToString(); + ReadOnlyMemory bodyInBytes = Encoding.UTF8.GetBytes(bodyString); + requestMessageBuilder.BodyParameters.Add("content-type", "application/xml"); + requestMessageBuilder.SetBody(bodyInBytes); + // var bodyInCharArr = Encoding.UTF8.GetString(requestMessageBuilder.Content).ToCharArray(); + + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/CopyObjectArgs.cs b/LibExternal/Minio/DataModel/Args/CopyObjectArgs.cs new file mode 100644 index 0000000..5ed929a --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/CopyObjectArgs.cs @@ -0,0 +1,252 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.DataModel.Encryption; +using Minio.DataModel.ObjectLock; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +public class CopyObjectArgs : ObjectWriteArgs +{ + public CopyObjectArgs() + { + RequestMethod = HttpMethod.Put; + SourceObject = new CopySourceObjectArgs(); + ReplaceTagsDirective = false; + ReplaceMetadataDirective = false; + ObjectLockSet = false; + RetentionUntilDate = default; + } + + internal CopySourceObjectArgs SourceObject { get; set; } + internal ObjectStat SourceObjectInfo { get; set; } + internal bool ReplaceTagsDirective { get; set; } + internal bool ReplaceMetadataDirective { get; set; } + internal string StorageClass { get; set; } + internal ObjectRetentionMode ObjectLockRetentionMode { get; set; } + internal DateTime RetentionUntilDate { get; set; } + internal bool ObjectLockSet { get; set; } + + internal override void Validate() + { + Utils.ValidateBucketName(BucketName); + if (SourceObject is null) + throw new InvalidOperationException(nameof(SourceObject) + " has not been assigned. Please use " + + nameof(WithCopyObjectSource)); + + if (SourceObjectInfo is null) + throw new InvalidOperationException( + "StatObject result for the copy source object needed to continue copy operation. Use " + + nameof(WithCopyObjectSourceStats) + " to initialize StatObject result."); + + if (!string.IsNullOrEmpty(NotMatchETag) && !string.IsNullOrEmpty(MatchETag)) + throw new InvalidOperationException("Invalid to set both Etag match conditions " + nameof(NotMatchETag) + + " and " + nameof(MatchETag)); + + if (!ModifiedSince.Equals(default) && + !UnModifiedSince.Equals(default)) + throw new InvalidOperationException("Invalid to set both modified date match conditions " + + nameof(ModifiedSince) + " and " + nameof(UnModifiedSince)); + + Populate(); + } + + private void Populate() + { + if (string.IsNullOrEmpty(ObjectName)) ObjectName = SourceObject.ObjectName; + if (SSE?.GetEncryptionType().Equals(EncryptionType.SSE_C) == true) + { + Headers = new Dictionary(StringComparer.Ordinal); + SSE.Marshal(Headers); + } + + if (!ReplaceMetadataDirective) + { + // Check in copy conditions if replace metadata has been set + var copyReplaceMeta = SourceObject.CopyOperationConditions?.HasReplaceMetadataDirective() == true; + WithReplaceMetadataDirective(copyReplaceMeta); + } + + Headers ??= new Dictionary(StringComparer.Ordinal); + if (ReplaceMetadataDirective) + { + if (Headers is not null) +#if NETSTANDARD + foreach (var pair in SourceObjectInfo.MetaData.ToList()) +#else + foreach (var pair in SourceObjectInfo.MetaData) +#endif + { + var comparer = StringComparer.OrdinalIgnoreCase; + var newDictionary = new Dictionary(Headers, comparer); + + SourceObjectInfo.MetaData.Remove(pair.Key); + } + + Headers = Headers + .Concat(SourceObjectInfo.MetaData) + .GroupBy(item => item.Key, StringComparer.Ordinal) + .ToDictionary(item => item.Key, item => + item.Last().Value, StringComparer.Ordinal); + } + + if (Headers is not null) + { + var newKVList = new List>(); +#if NETSTANDARD + foreach (var item in Headers.ToList()) +#else + foreach (var item in Headers) +#endif + { + var key = item.Key; + if (!OperationsUtil.IsSupportedHeader(item.Key) && + !item.Key.StartsWith("x-amz-meta", + StringComparison.OrdinalIgnoreCase) && + !OperationsUtil.IsSSEHeader(key)) + { + newKVList.Add(new Tuple("x-amz-meta-" + + key.ToLowerInvariant(), item.Value)); + Headers.Remove(item.Key); + } + + newKVList.Add(new Tuple(key, item.Value)); + } + + foreach (var item in newKVList) Headers[item.Item1] = item.Item2; + } + } + + public CopyObjectArgs WithCopyObjectSource(CopySourceObjectArgs cs) + { + if (cs is null) + throw new InvalidOperationException("The copy source object needed for copy operation is not initialized."); + + SourceObject.RequestMethod = HttpMethod.Put; + SourceObject ??= new CopySourceObjectArgs(); + SourceObject.BucketName = cs.BucketName; + SourceObject.ObjectName = cs.ObjectName; + SourceObject.VersionId = cs.VersionId; + SourceObject.SSE = cs.SSE; + SourceObject.Headers = cs.Headers; + SourceObject.MatchETag = cs.MatchETag; + SourceObject.ModifiedSince = cs.ModifiedSince; + SourceObject.NotMatchETag = cs.NotMatchETag; + SourceObject.UnModifiedSince = cs.UnModifiedSince; + SourceObject.CopySourceObjectPath = $"{cs.BucketName}/{Utils.UrlEncode(cs.ObjectName)}"; + SourceObject.CopyOperationConditions = cs.CopyOperationConditions?.Clone(); + return this; + } + + public CopyObjectArgs WithReplaceTagsDirective(bool replace) + { + ReplaceTagsDirective = replace; + return this; + } + + public CopyObjectArgs WithReplaceMetadataDirective(bool replace) + { + ReplaceMetadataDirective = replace; + return this; + } + + public CopyObjectArgs WithObjectLockMode(ObjectRetentionMode mode) + { + ObjectLockSet = true; + ObjectLockRetentionMode = mode; + return this; + } + + public CopyObjectArgs WithObjectLockRetentionDate(DateTime untilDate) + { + ObjectLockSet = true; + RetentionUntilDate = new DateTime(untilDate.Year, untilDate.Month, untilDate.Day, + untilDate.Hour, untilDate.Minute, untilDate.Second, untilDate.Kind); + return this; + } + + internal CopyObjectArgs WithCopyObjectSourceStats(ObjectStat info) + { + SourceObjectInfo = info; + if (info.MetaData is not null && !ReplaceMetadataDirective) + { + SourceObject.Headers ??= new Dictionary(StringComparer.Ordinal); + SourceObject.Headers = SourceObject.Headers.Concat(info.MetaData) + .GroupBy(item => item.Key, StringComparer.Ordinal) + .ToDictionary(item => item.Key, item => item.First().Value, StringComparer.Ordinal); + } + + return this; + } + + internal CopyObjectArgs WithStorageClass(string storageClass) + { + StorageClass = storageClass; + return this; + } + + public CopyObjectArgs WithRetentionUntilDate(DateTime date) + { + ObjectLockSet = true; + RetentionUntilDate = date; + return this; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + if (!string.IsNullOrEmpty(MatchETag)) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-copy-source-if-match", MatchETag); + if (!string.IsNullOrEmpty(NotMatchETag)) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-copy-source-if-none-match", NotMatchETag); + if (ModifiedSince != default) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-copy-source-if-unmodified-since", + Utils.To8601String(ModifiedSince)); + + if (UnModifiedSince != default) + { + using var request = requestMessageBuilder.Request; + request.Headers.Add("x-amz-copy-source-if-modified-since", + Utils.To8601String(UnModifiedSince)); + } + + if (!string.IsNullOrEmpty(VersionId)) requestMessageBuilder.AddQueryParameter("versionId", VersionId); + if (ObjectTags?.TaggingSet?.Tag.Count > 0) + { + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-tagging", ObjectTags.GetTagString()); + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-tagging-directive", + ReplaceTagsDirective ? "COPY" : "REPLACE"); + } + + if (ReplaceMetadataDirective) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-metadata-directive", "REPLACE"); + if (!string.IsNullOrEmpty(StorageClass)) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-storage-class", StorageClass); + if (LegalHoldEnabled == true) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-object-lock-legal-hold", "ON"); + if (ObjectLockSet) + { + if (!RetentionUntilDate.Equals(default)) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-object-lock-retain-until-date", + Utils.To8601String(RetentionUntilDate)); + + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-object-lock-mode", + ObjectLockRetentionMode == ObjectRetentionMode.GOVERNANCE ? "GOVERNANCE" : "COMPLIANCE"); + } + + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/CopyObjectRequestArgs.cs b/LibExternal/Minio/DataModel/Args/CopyObjectRequestArgs.cs new file mode 100644 index 0000000..a2f1a4f --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/CopyObjectRequestArgs.cs @@ -0,0 +1,189 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.DataModel.ObjectLock; +using Minio.DataModel.Result; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +internal class CopyObjectRequestArgs : ObjectWriteArgs +{ + internal CopyObjectRequestArgs() + { + RequestMethod = HttpMethod.Put; + Headers = new Dictionary(StringComparer.Ordinal); + CopyOperationObjectType = typeof(CopyObjectResult); + } + + internal CopySourceObjectArgs SourceObject { get; set; } + internal ObjectStat SourceObjectInfo { get; set; } + internal Type CopyOperationObjectType { get; set; } + internal bool ReplaceTagsDirective { get; set; } + internal bool ReplaceMetadataDirective { get; set; } + internal string StorageClass { get; set; } + internal Dictionary QueryMap { get; set; } + internal CopyConditions CopyCondition { get; set; } + internal ObjectRetentionMode ObjectLockRetentionMode { get; set; } + internal DateTime RetentionUntilDate { get; set; } + internal bool ObjectLockSet { get; set; } + + internal CopyObjectRequestArgs WithQueryMap(IDictionary queryMap) + { + QueryMap = new Dictionary(queryMap, StringComparer.Ordinal); + return this; + } + + internal CopyObjectRequestArgs WithPartCondition(CopyConditions partCondition) + { + CopyCondition = partCondition.Clone(); + Headers ??= new Dictionary(StringComparer.Ordinal); + Headers["x-amz-copy-source-range"] = "bytes=" + partCondition.byteRangeStart + "-" + partCondition.byteRangeEnd; + + return this; + } + + internal CopyObjectRequestArgs WithReplaceMetadataDirective(bool replace) + { + ReplaceMetadataDirective = replace; + return this; + } + + internal CopyObjectRequestArgs WithReplaceTagsDirective(bool replace) + { + ReplaceTagsDirective = replace; + return this; + } + + public CopyObjectRequestArgs WithCopyObjectSource(CopySourceObjectArgs cs) + { + if (cs is null) + throw new InvalidOperationException("The copy source object needed for copy operation is not initialized."); + + SourceObject ??= new CopySourceObjectArgs(); + SourceObject.RequestMethod = HttpMethod.Put; + SourceObject.BucketName = cs.BucketName; + SourceObject.ObjectName = cs.ObjectName; + SourceObject.VersionId = cs.VersionId; + SourceObject.SSE = cs.SSE; + SourceObject.Headers = new Dictionary(cs.Headers, StringComparer.Ordinal); + SourceObject.MatchETag = cs.MatchETag; + SourceObject.ModifiedSince = cs.ModifiedSince; + SourceObject.NotMatchETag = cs.NotMatchETag; + SourceObject.UnModifiedSince = cs.UnModifiedSince; + SourceObject.CopySourceObjectPath = $"{cs.BucketName}/{Utils.UrlEncode(cs.ObjectName)}"; + SourceObject.CopyOperationConditions = cs.CopyOperationConditions?.Clone(); + return this; + } + + public CopyObjectRequestArgs WithSourceObjectInfo(ObjectStat stat) + { + SourceObjectInfo = stat; + return this; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + var sourceObjectPath = SourceObject.BucketName + "/" + Utils.UrlEncode(SourceObject.ObjectName); + if (!string.IsNullOrEmpty(SourceObject.VersionId)) sourceObjectPath += "?versionId=" + SourceObject.VersionId; + // Set the object source + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-copy-source", sourceObjectPath); + + if (QueryMap is not null) + foreach (var query in QueryMap) + requestMessageBuilder.AddQueryParameter(query.Key, query.Value); + + if (SourceObject.CopyOperationConditions is not null) + foreach (var item in SourceObject.CopyOperationConditions.Conditions) + requestMessageBuilder.AddOrUpdateHeaderParameter(item.Key, item.Value); + + if (!string.IsNullOrEmpty(MatchETag)) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-copy-source-if-match", MatchETag); + if (!string.IsNullOrEmpty(NotMatchETag)) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-copy-source-if-none-match", NotMatchETag); + if (ModifiedSince != default) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-copy-source-if-unmodified-since", + Utils.To8601String(ModifiedSince)); + + if (UnModifiedSince != default) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-copy-source-if-modified-since", + Utils.To8601String(UnModifiedSince)); + + if (ObjectTags?.TaggingSet?.Tag.Count > 0) + { + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-tagging", ObjectTags.GetTagString()); + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-tagging-directive", + ReplaceTagsDirective ? "REPLACE" : "COPY"); + if (ReplaceMetadataDirective) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-tagging-directive", "REPLACE"); + } + + if (ReplaceMetadataDirective) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-metadata-directive", "REPLACE"); + if (!string.IsNullOrEmpty(StorageClass)) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-storage-class", StorageClass); + if (ObjectLockSet) + { + if (!RetentionUntilDate.Equals(default)) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-object-lock-retain-until-date", + Utils.To8601String(RetentionUntilDate)); + + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-object-lock-mode", + ObjectLockRetentionMode == ObjectRetentionMode.GOVERNANCE ? "GOVERNANCE" : "COMPLIANCE"); + } + + if (!RequestBody.IsEmpty) requestMessageBuilder.SetBody(RequestBody); + return requestMessageBuilder; + } + + internal CopyObjectRequestArgs WithCopyOperationObjectType(Type cp) + { + CopyOperationObjectType = cp; + return this; + } + + public CopyObjectRequestArgs WithObjectLockMode(ObjectRetentionMode mode) + { + ObjectLockSet = true; + ObjectLockRetentionMode = mode; + return this; + } + + public CopyObjectRequestArgs WithObjectLockRetentionDate(DateTime untilDate) + { + ObjectLockSet = true; + RetentionUntilDate = new DateTime(untilDate.Year, untilDate.Month, untilDate.Day, + untilDate.Hour, untilDate.Minute, untilDate.Second, untilDate.Kind); + return this; + } + + internal override void Validate() + { + Utils.ValidateBucketName(BucketName); //Object name can be same as that of source. + if (SourceObject is null) throw new InvalidOperationException(nameof(SourceObject) + " has not been assigned."); + Populate(); + } + + internal void Populate() + { + ObjectName = string.IsNullOrEmpty(ObjectName) ? SourceObject.ObjectName : ObjectName; + // Opting for concat as Headers may have byte range info .etc. + if (!ReplaceMetadataDirective && SourceObjectInfo.MetaData is not null) + Headers = SourceObjectInfo.MetaData.Concat(Headers).GroupBy(item => item.Key, StringComparer.Ordinal) + .ToDictionary(item => item.Key, item => item.First().Value, StringComparer.Ordinal); + else if (ReplaceMetadataDirective) Headers ??= new Dictionary(StringComparer.Ordinal); + } +} diff --git a/LibExternal/Minio/DataModel/Args/CopySourceObjectArgs.cs b/LibExternal/Minio/DataModel/Args/CopySourceObjectArgs.cs new file mode 100644 index 0000000..a0b151a --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/CopySourceObjectArgs.cs @@ -0,0 +1,41 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class CopySourceObjectArgs : ObjectConditionalQueryArgs +{ + public CopySourceObjectArgs() + { + RequestMethod = HttpMethod.Put; + CopyOperationConditions = new CopyConditions(); + Headers = new Dictionary(StringComparer.Ordinal); + } + + internal string CopySourceObjectPath { get; set; } + internal CopyConditions CopyOperationConditions { get; set; } + + public CopySourceObjectArgs WithCopyConditions(CopyConditions cp) + { + CopyOperationConditions = cp is not null ? cp.Clone() : new CopyConditions(); + return this; + } + + // internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + // { + // return requestMessageBuilder; + // } +} diff --git a/LibExternal/Minio/DataModel/Args/EncryptionArgs.cs b/LibExternal/Minio/DataModel/Args/EncryptionArgs.cs new file mode 100644 index 0000000..fb68315 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/EncryptionArgs.cs @@ -0,0 +1,31 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.DataModel.Encryption; + +namespace Minio.DataModel.Args; + +public abstract class EncryptionArgs : ObjectArgs + where T : EncryptionArgs +{ + internal IServerSideEncryption SSE { get; set; } + + public T WithServerSideEncryption(IServerSideEncryption sse) + { + SSE = sse; + return (T)this; + } +} diff --git a/LibExternal/Minio/DataModel/Args/GetBucketEncryptionArgs.cs b/LibExternal/Minio/DataModel/Args/GetBucketEncryptionArgs.cs new file mode 100644 index 0000000..00ab2b8 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/GetBucketEncryptionArgs.cs @@ -0,0 +1,31 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class GetBucketEncryptionArgs : BucketArgs +{ + public GetBucketEncryptionArgs() + { + RequestMethod = HttpMethod.Get; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("encryption", ""); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/GetBucketLifecycleArgs.cs b/LibExternal/Minio/DataModel/Args/GetBucketLifecycleArgs.cs new file mode 100644 index 0000000..3df1585 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/GetBucketLifecycleArgs.cs @@ -0,0 +1,31 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class GetBucketLifecycleArgs : BucketArgs +{ + public GetBucketLifecycleArgs() + { + RequestMethod = HttpMethod.Get; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("lifecycle", ""); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/GetBucketNotificationsArgs.cs b/LibExternal/Minio/DataModel/Args/GetBucketNotificationsArgs.cs new file mode 100644 index 0000000..9a5c786 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/GetBucketNotificationsArgs.cs @@ -0,0 +1,31 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class GetBucketNotificationsArgs : BucketArgs +{ + public GetBucketNotificationsArgs() + { + RequestMethod = HttpMethod.Get; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("notification", ""); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/GetBucketReplicationArgs.cs b/LibExternal/Minio/DataModel/Args/GetBucketReplicationArgs.cs new file mode 100644 index 0000000..63dea4e --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/GetBucketReplicationArgs.cs @@ -0,0 +1,31 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class GetBucketReplicationArgs : BucketArgs +{ + public GetBucketReplicationArgs() + { + RequestMethod = HttpMethod.Get; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("replication", ""); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/GetBucketTagsArgs.cs b/LibExternal/Minio/DataModel/Args/GetBucketTagsArgs.cs new file mode 100644 index 0000000..88e0234 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/GetBucketTagsArgs.cs @@ -0,0 +1,31 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class GetBucketTagsArgs : BucketArgs +{ + public GetBucketTagsArgs() + { + RequestMethod = HttpMethod.Get; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("tagging", ""); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/GetMultipartUploadsListArgs.cs b/LibExternal/Minio/DataModel/Args/GetMultipartUploadsListArgs.cs new file mode 100644 index 0000000..eb978a7 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/GetMultipartUploadsListArgs.cs @@ -0,0 +1,69 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Globalization; + +namespace Minio.DataModel.Args; + +public class GetMultipartUploadsListArgs : BucketArgs +{ + public GetMultipartUploadsListArgs() + { + RequestMethod = HttpMethod.Get; + MAX_UPLOAD_COUNT = 1000; + } + + internal string Prefix { get; private set; } + internal string Delimiter { get; private set; } + internal string KeyMarker { get; private set; } + internal string UploadIdMarker { get; private set; } + internal uint MAX_UPLOAD_COUNT { get; } + + public GetMultipartUploadsListArgs WithPrefix(string prefix) + { + Prefix = prefix ?? string.Empty; + return this; + } + + public GetMultipartUploadsListArgs WithDelimiter(string delim) + { + Delimiter = delim ?? string.Empty; + return this; + } + + public GetMultipartUploadsListArgs WithKeyMarker(string nextKeyMarker) + { + KeyMarker = nextKeyMarker ?? string.Empty; + return this; + } + + public GetMultipartUploadsListArgs WithUploadIdMarker(string nextUploadIdMarker) + { + UploadIdMarker = nextUploadIdMarker ?? string.Empty; + return this; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("uploads", ""); + requestMessageBuilder.AddQueryParameter("prefix", Prefix); + requestMessageBuilder.AddQueryParameter("delimiter", Delimiter); + requestMessageBuilder.AddQueryParameter("key-marker", KeyMarker); + requestMessageBuilder.AddQueryParameter("upload-id-marker", UploadIdMarker); + requestMessageBuilder.AddQueryParameter("max-uploads", MAX_UPLOAD_COUNT.ToString(CultureInfo.InvariantCulture)); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/GetObjectArgs.cs b/LibExternal/Minio/DataModel/Args/GetObjectArgs.cs new file mode 100644 index 0000000..4971787 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/GetObjectArgs.cs @@ -0,0 +1,121 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.DataModel.Encryption; +using Minio.Exceptions; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +public class GetObjectArgs : ObjectConditionalQueryArgs +{ + public GetObjectArgs() + { + RequestMethod = HttpMethod.Get; + OffsetLengthSet = false; + } + + internal Action CallBack { get; private set; } + internal Func FuncCallBack { get; private set; } + internal long ObjectOffset { get; private set; } + internal long ObjectLength { get; private set; } + internal string FileName { get; private set; } + internal bool OffsetLengthSet { get; set; } + + internal override void Validate() + { + base.Validate(); + if (CallBack is null && FuncCallBack is null && string.IsNullOrEmpty(FileName)) + throw new MinioException("Atleast one of " + nameof(CallBack) + ", CallBack method or " + nameof(FileName) + + " file path to save need to be set for GetObject operation."); + + if (OffsetLengthSet) + { + if (ObjectOffset < 0) + throw new InvalidDataException("Offset should be zero or greater: " + nameof(ObjectOffset)); + + if (ObjectLength < 0) + throw new InvalidDataException( + "Length should be greater than or equal to zero: " + nameof(ObjectLength)); + } + + if (FileName is not null) Utils.ValidateFile(FileName); + Populate(); + } + + private void Populate() + { + Headers ??= new Dictionary(StringComparer.Ordinal); + if (SSE?.GetEncryptionType().Equals(EncryptionType.SSE_C) == true) SSE.Marshal(Headers); + + if (OffsetLengthSet) + { + // "Range" header accepts byte start index and end index + if (ObjectLength > 0 && ObjectOffset > 0) + Headers["Range"] = "bytes=" + ObjectOffset + "-" + (ObjectOffset + ObjectLength - 1); + else if (ObjectLength == 0 && ObjectOffset > 0) + Headers["Range"] = "bytes=" + ObjectOffset + "-"; + else if (ObjectLength > 0 && ObjectOffset == 0) Headers["Range"] = "bytes=0-" + (ObjectLength - 1); + } + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + if (!string.IsNullOrEmpty(VersionId)) requestMessageBuilder.AddQueryParameter("versionId", $"{VersionId}"); + + if (CallBack is not null) requestMessageBuilder.ResponseWriter = CallBack; + else requestMessageBuilder.FunctionResponseWriter = FuncCallBack; + + if (Headers.TryGetValue(S3ZipExtractKey, out var value)) + requestMessageBuilder.AddQueryParameter(S3ZipExtractKey, value); + + return requestMessageBuilder; + } + + public GetObjectArgs WithCallbackStream(Action cb) + { + CallBack = cb; + return this; + } + + public GetObjectArgs WithCallbackStream(Func cb) + { + FuncCallBack = cb; + return this; + } + + public GetObjectArgs WithOffsetAndLength(long offset, long length) + { + OffsetLengthSet = true; + ObjectOffset = offset < 0 ? 0 : offset; + ObjectLength = length < 0 ? 0 : length; + return this; + } + + public GetObjectArgs WithLength(long length) + { + OffsetLengthSet = true; + ObjectOffset = 0; + ObjectLength = length < 0 ? 0 : length; + return this; + } + + public GetObjectArgs WithFile(string file) + { + FileName = file; + return this; + } +} diff --git a/LibExternal/Minio/DataModel/Args/GetObjectLegalHoldArgs.cs b/LibExternal/Minio/DataModel/Args/GetObjectLegalHoldArgs.cs new file mode 100644 index 0000000..c5e5ff3 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/GetObjectLegalHoldArgs.cs @@ -0,0 +1,32 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class GetObjectLegalHoldArgs : ObjectVersionArgs +{ + public GetObjectLegalHoldArgs() + { + RequestMethod = HttpMethod.Get; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("legal-hold", ""); + if (!string.IsNullOrEmpty(VersionId)) requestMessageBuilder.AddQueryParameter("versionId", VersionId); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/GetObjectListArgs.cs b/LibExternal/Minio/DataModel/Args/GetObjectListArgs.cs new file mode 100644 index 0000000..54586f0 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/GetObjectListArgs.cs @@ -0,0 +1,116 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +internal class GetObjectListArgs : BucketArgs +{ + public GetObjectListArgs() + { + RequestMethod = HttpMethod.Get; + // Avoiding null values. Default is empty strings. + Delimiter = string.Empty; + Prefix = string.Empty; + UseV2 = true; + Versions = false; + Marker = string.Empty; + } + + internal string Delimiter { get; private set; } + internal string Prefix { get; private set; } + internal bool UseV2 { get; private set; } + internal string Marker { get; private set; } + internal string VersionIdMarker { get; private set; } + internal bool Versions { get; private set; } + internal string ContinuationToken { get; set; } + + public GetObjectListArgs WithDelimiter(string delim) + { + Delimiter = delim ?? string.Empty; + return this; + } + + public GetObjectListArgs WithPrefix(string prefix) + { + Prefix = prefix ?? string.Empty; + return this; + } + + public GetObjectListArgs WithMarker(string marker) + { + Marker = string.IsNullOrWhiteSpace(marker) ? string.Empty : marker; + return this; + } + + public GetObjectListArgs WithVersionIdMarker(string marker) + { + VersionIdMarker = string.IsNullOrWhiteSpace(marker) ? string.Empty : marker; + return this; + } + + public GetObjectListArgs WithVersions(bool versions) + { + Versions = versions; + return this; + } + + public GetObjectListArgs WithContinuationToken(string token) + { + ContinuationToken = string.IsNullOrWhiteSpace(token) ? string.Empty : token; + return this; + } + + public GetObjectListArgs WithListObjectsV1(bool useV1) + { + UseV2 = !useV1; + return this; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + foreach (var h in Headers) + requestMessageBuilder.AddOrUpdateHeaderParameter(h.Key, h.Value); + + requestMessageBuilder.AddQueryParameter("delimiter", Delimiter); + requestMessageBuilder.AddQueryParameter("max-keys", "1000"); + requestMessageBuilder.AddQueryParameter("encoding-type", "url"); + requestMessageBuilder.AddQueryParameter("prefix", Prefix); + if (Versions) + { + requestMessageBuilder.AddQueryParameter("versions", ""); + if (!string.IsNullOrWhiteSpace(Marker)) requestMessageBuilder.AddQueryParameter("key-marker", Marker); + if (!string.IsNullOrWhiteSpace(VersionIdMarker)) + requestMessageBuilder.AddQueryParameter("version-id-marker", VersionIdMarker); + } + else if (!Versions && UseV2) + { + requestMessageBuilder.AddQueryParameter("list-type", "2"); + if (!string.IsNullOrWhiteSpace(Marker)) requestMessageBuilder.AddQueryParameter("start-after", Marker); + if (!string.IsNullOrWhiteSpace(ContinuationToken)) + requestMessageBuilder.AddQueryParameter("continuation-token", ContinuationToken); + } + else if (!Versions && !UseV2) + { + requestMessageBuilder.AddQueryParameter("marker", Marker); + } + else + { + throw new InvalidOperationException("Wrong set of properties set."); + } + + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/GetObjectLockConfigurationArgs.cs b/LibExternal/Minio/DataModel/Args/GetObjectLockConfigurationArgs.cs new file mode 100644 index 0000000..e269cc6 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/GetObjectLockConfigurationArgs.cs @@ -0,0 +1,31 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class GetObjectLockConfigurationArgs : BucketArgs +{ + public GetObjectLockConfigurationArgs() + { + RequestMethod = HttpMethod.Get; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("object-lock", ""); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/GetObjectRetentionArgs.cs b/LibExternal/Minio/DataModel/Args/GetObjectRetentionArgs.cs new file mode 100644 index 0000000..e7121ef --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/GetObjectRetentionArgs.cs @@ -0,0 +1,32 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class GetObjectRetentionArgs : ObjectVersionArgs +{ + public GetObjectRetentionArgs() + { + RequestMethod = HttpMethod.Get; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("retention", ""); + if (!string.IsNullOrEmpty(VersionId)) requestMessageBuilder.AddQueryParameter("versionId", VersionId); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/GetObjectTagsArgs.cs b/LibExternal/Minio/DataModel/Args/GetObjectTagsArgs.cs new file mode 100644 index 0000000..4ec647d --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/GetObjectTagsArgs.cs @@ -0,0 +1,32 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class GetObjectTagsArgs : ObjectVersionArgs +{ + public GetObjectTagsArgs() + { + RequestMethod = HttpMethod.Get; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("tagging", ""); + if (!string.IsNullOrEmpty(VersionId)) requestMessageBuilder.AddQueryParameter("versionId", VersionId); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/GetPolicyArgs.cs b/LibExternal/Minio/DataModel/Args/GetPolicyArgs.cs new file mode 100644 index 0000000..2f4cfd6 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/GetPolicyArgs.cs @@ -0,0 +1,31 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class GetPolicyArgs : BucketArgs +{ + public GetPolicyArgs() + { + RequestMethod = HttpMethod.Get; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("policy", ""); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/GetVersioningArgs.cs b/LibExternal/Minio/DataModel/Args/GetVersioningArgs.cs new file mode 100644 index 0000000..54fbf4b --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/GetVersioningArgs.cs @@ -0,0 +1,31 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class GetVersioningArgs : BucketArgs +{ + public GetVersioningArgs() + { + RequestMethod = HttpMethod.Get; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("versioning", ""); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/ListIncompleteUploadsArgs.cs b/LibExternal/Minio/DataModel/Args/ListIncompleteUploadsArgs.cs new file mode 100644 index 0000000..2623e76 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/ListIncompleteUploadsArgs.cs @@ -0,0 +1,49 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class ListIncompleteUploadsArgs : BucketArgs +{ + public ListIncompleteUploadsArgs() + { + RequestMethod = HttpMethod.Get; + Recursive = true; + } + + internal string Prefix { get; private set; } + internal string Delimiter { get; private set; } + internal bool Recursive { get; private set; } + + public ListIncompleteUploadsArgs WithPrefix(string prefix) + { + Prefix = prefix ?? string.Empty; + return this; + } + + public ListIncompleteUploadsArgs WithDelimiter(string delim) + { + Delimiter = delim ?? string.Empty; + return this; + } + + public ListIncompleteUploadsArgs WithRecursive(bool recursive) + { + Recursive = recursive; + Delimiter = recursive ? string.Empty : "/"; + return this; + } +} diff --git a/LibExternal/Minio/DataModel/Args/ListObjectsArgs.cs b/LibExternal/Minio/DataModel/Args/ListObjectsArgs.cs new file mode 100644 index 0000000..c88360f --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/ListObjectsArgs.cs @@ -0,0 +1,55 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class ListObjectsArgs : BucketArgs +{ + public ListObjectsArgs() + { + UseV2 = true; + Versions = false; + } + + internal string Prefix { get; private set; } + internal bool Recursive { get; private set; } + internal bool Versions { get; private set; } + internal bool UseV2 { get; private set; } + + public ListObjectsArgs WithPrefix(string prefix) + { + Prefix = prefix; + return this; + } + + public ListObjectsArgs WithRecursive(bool rec) + { + Recursive = rec; + return this; + } + + public ListObjectsArgs WithVersions(bool ver) + { + Versions = ver; + return this; + } + + public ListObjectsArgs WithListObjectsV1(bool useV1) + { + UseV2 = !useV1; + return this; + } +} diff --git a/LibExternal/Minio/DataModel/Args/ListenBucketNotificationsArgs.cs b/LibExternal/Minio/DataModel/Args/ListenBucketNotificationsArgs.cs new file mode 100644 index 0000000..856b453 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/ListenBucketNotificationsArgs.cs @@ -0,0 +1,125 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Globalization; +using Minio.DataModel.Notification; +using Minio.Handlers; + +namespace Minio.DataModel.Args; + +public class ListenBucketNotificationsArgs : BucketArgs +{ + internal readonly IEnumerable NoErrorHandlers = + Enumerable.Empty(); + + public ListenBucketNotificationsArgs() + { + RequestMethod = HttpMethod.Get; + EnableTrace = false; + Events = new List(); + Prefix = ""; + Suffix = ""; + } + + internal string Prefix { get; private set; } + internal string Suffix { get; private set; } + internal List Events { get; } + internal IObserver NotificationObserver { get; private set; } + public bool EnableTrace { get; private set; } + + public override string ToString() + { + var str = string.Join("\n", string.Format(CultureInfo.InvariantCulture, "\nRequestMethod= {0}", RequestMethod), + string.Format(CultureInfo.InvariantCulture, "EnableTrace= {0}", EnableTrace)); + + var eventsAsStr = ""; + foreach (var eventType in Events) + { + if (!string.IsNullOrEmpty(eventsAsStr)) + eventsAsStr += ", "; + eventsAsStr += eventType.Value; + } + + return string.Join("\n", str, string.Format(CultureInfo.InvariantCulture, "Events= [{0}]", eventsAsStr), + string.Format(CultureInfo.InvariantCulture, "Prefix= {0}", Prefix), + string.Format(CultureInfo.InvariantCulture, "Suffix= {0}\n", Suffix)); + } + + public ListenBucketNotificationsArgs WithNotificationObserver(IObserver obs) + { + NotificationObserver = obs; + return this; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + + { + foreach (var eventType in Events) requestMessageBuilder.AddQueryParameter("events", eventType.Value); + requestMessageBuilder.AddQueryParameter("prefix", Prefix); + requestMessageBuilder.AddQueryParameter("suffix", Suffix); + + requestMessageBuilder.FunctionResponseWriter = async (responseStream, cancellationToken) => + { + using var sr = new StreamReader(responseStream); + while (!sr.EndOfStream) + try + { + var line = await sr.ReadLineAsync().ConfigureAwait(false); + if (string.IsNullOrEmpty(line)) + break; + + if (EnableTrace) + { + Console.WriteLine("== ListenBucketNotificationsAsync read line =="); + Console.WriteLine(line); + Console.WriteLine("=============================================="); + } + + var trimmed = line.Trim(); + if (trimmed.Length > 2) NotificationObserver.OnNext(new MinioNotificationRaw(trimmed)); + } + catch + { + break; + } + }; + return requestMessageBuilder; + } + + internal ListenBucketNotificationsArgs WithEnableTrace(bool trace) + { + EnableTrace = trace; + return this; + } + + public ListenBucketNotificationsArgs WithPrefix(string prefix) + { + Prefix = prefix; + return this; + } + + public ListenBucketNotificationsArgs WithSuffix(string suffix) + { + Suffix = suffix; + return this; + } + + public ListenBucketNotificationsArgs WithEvents(IList events) + { + Events.AddRange(events); + return this; + } +} diff --git a/LibExternal/Minio/DataModel/Args/MakeBucketArgs.cs b/LibExternal/Minio/DataModel/Args/MakeBucketArgs.cs new file mode 100644 index 0000000..1445e11 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/MakeBucketArgs.cs @@ -0,0 +1,60 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Text; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +public class MakeBucketArgs : BucketArgs +{ + public MakeBucketArgs() + { + RequestMethod = HttpMethod.Put; + } + + internal string Location { get; set; } + internal bool ObjectLock { get; set; } + + public MakeBucketArgs WithLocation(string loc) + { + Location = loc; + return this; + } + + public MakeBucketArgs WithObjectLock() + { + ObjectLock = true; + return this; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + // ``us-east-1`` is not a valid location constraint according to amazon, so we skip it. + if (!string.IsNullOrEmpty(Location) && + !string.Equals(Location, "us-east-1", StringComparison.OrdinalIgnoreCase)) + { + var config = new CreateBucketConfiguration(Location); + var body = Utils.MarshalXML(config, "http://s3.amazonaws.com/doc/2006-03-01/"); + requestMessageBuilder.AddXmlBody(body); + requestMessageBuilder.AddOrUpdateHeaderParameter("Content-Md5", + Utils.GetMD5SumStr(Encoding.UTF8.GetBytes(body))); + } + + if (ObjectLock) requestMessageBuilder.AddOrUpdateHeaderParameter("X-Amz-Bucket-Object-Lock-Enabled", "true"); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/MultipartCopyUploadArgs.cs b/LibExternal/Minio/DataModel/Args/MultipartCopyUploadArgs.cs new file mode 100644 index 0000000..889785a --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/MultipartCopyUploadArgs.cs @@ -0,0 +1,156 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.DataModel.ObjectLock; +using Minio.DataModel.Tags; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +internal class MultipartCopyUploadArgs : ObjectWriteArgs +{ + internal MultipartCopyUploadArgs(CopyObjectArgs args) + { + if (args is null || args.SourceObject is null) + { + var message = args is null + ? "The constructor of " + nameof(CopyObjectRequestArgs) + + "initialized with arguments of CopyObjectArgs null." + : "The constructor of " + nameof(CopyObjectRequestArgs) + + "initialized with arguments of CopyObjectArgs type but with " + nameof(args.SourceObject) + + " not initialized."; + throw new InvalidOperationException(message); + } + + RequestMethod = HttpMethod.Put; + + SourceObject = new CopySourceObjectArgs + { + BucketName = args.SourceObject.BucketName, + ObjectName = args.SourceObject.ObjectName, + VersionId = args.SourceObject.VersionId, + CopyOperationConditions = args.SourceObject.CopyOperationConditions.Clone(), + MatchETag = args.SourceObject.MatchETag, + ModifiedSince = args.SourceObject.ModifiedSince, + NotMatchETag = args.SourceObject.NotMatchETag, + UnModifiedSince = args.SourceObject.UnModifiedSince + }; + + // Destination part. + BucketName = args.BucketName; + ObjectName = args.ObjectName ?? args.SourceObject.ObjectName; + SSE = args.SSE; + SSE?.Marshal(Headers); + VersionId = args.VersionId; + SourceObjectInfo = args.SourceObjectInfo; + // Header part + if (!args.ReplaceMetadataDirective) + Headers = new Dictionary(args.SourceObjectInfo.MetaData, StringComparer.Ordinal); + else if (args.ReplaceMetadataDirective) Headers ??= new Dictionary(StringComparer.Ordinal); + if (Headers is not null) + { + var newKVList = new List>(); + foreach (var item in Headers) + { + var key = item.Key; + if (!OperationsUtil.IsSupportedHeader(item.Key) && + !item.Key.StartsWith("x-amz-meta", StringComparison.OrdinalIgnoreCase) && + !OperationsUtil.IsSSEHeader(key)) + newKVList.Add(new Tuple("x-amz-meta-" + key.ToLowerInvariant(), item.Value)); + } + + foreach (var item in newKVList) Headers[item.Item1] = item.Item2; + } + + ReplaceTagsDirective = args.ReplaceTagsDirective; + if (args.ReplaceTagsDirective && args.ObjectTags?.TaggingSet.Tag.Count > 0) // Tags of Source object + ObjectTags = Tagging.GetObjectTags(args.ObjectTags.Tags); + } + + internal MultipartCopyUploadArgs() + { + RequestMethod = HttpMethod.Put; + } + + internal CopySourceObjectArgs SourceObject { get; set; } + internal ObjectStat SourceObjectInfo { get; set; } + internal long CopySize { get; set; } + internal bool ReplaceMetadataDirective { get; set; } + internal bool ReplaceTagsDirective { get; set; } + internal string StorageClass { get; set; } + internal ObjectRetentionMode ObjectLockRetentionMode { get; set; } + internal DateTime RetentionUntilDate { get; set; } + internal bool ObjectLockSet { get; set; } + + internal MultipartCopyUploadArgs WithCopySize(long copySize) + { + CopySize = copySize; + return this; + } + + internal MultipartCopyUploadArgs WithStorageClass(string storageClass) + { + StorageClass = storageClass; + return this; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + if (ObjectTags?.TaggingSet?.Tag.Count > 0) + { + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-tagging", ObjectTags.GetTagString()); + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-tagging-directive", + ReplaceTagsDirective ? "REPLACE" : "COPY"); + } + + if (ReplaceMetadataDirective) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-metadata-directive", "REPLACE"); + if (!string.IsNullOrEmpty(StorageClass)) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-storage-class", StorageClass); + if (ObjectLockSet) + { + if (!RetentionUntilDate.Equals(default)) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-object-lock-retain-until-date", + Utils.To8601String(RetentionUntilDate)); + + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-object-lock-mode", + ObjectLockRetentionMode == ObjectRetentionMode.GOVERNANCE ? "GOVERNANCE" : "COMPLIANCE"); + } + + return requestMessageBuilder; + } + + internal MultipartCopyUploadArgs WithReplaceMetadataDirective(bool replace) + { + ReplaceMetadataDirective = replace; + return this; + } + + internal MultipartCopyUploadArgs WithObjectLockMode(ObjectRetentionMode mode) + { + ObjectLockSet = true; + ObjectLockRetentionMode = mode; + return this; + } + + internal MultipartCopyUploadArgs WithObjectLockRetentionDate(DateTime untilDate) + { + ObjectLockSet = true; + RetentionUntilDate = new DateTime(untilDate.Year, untilDate.Month, untilDate.Day, + untilDate.Hour, untilDate.Minute, untilDate.Second, untilDate.Kind); + return this; + } +} diff --git a/LibExternal/Minio/DataModel/Args/NewMultipartUploadArgs.cs b/LibExternal/Minio/DataModel/Args/NewMultipartUploadArgs.cs new file mode 100644 index 0000000..51db860 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/NewMultipartUploadArgs.cs @@ -0,0 +1,66 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.DataModel.ObjectLock; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +internal class NewMultipartUploadArgs : ObjectWriteArgs + where T : NewMultipartUploadArgs +{ + internal NewMultipartUploadArgs() + { + RequestMethod = HttpMethod.Post; + } + + internal ObjectRetentionMode ObjectLockRetentionMode { get; set; } + internal DateTime RetentionUntilDate { get; set; } + internal bool ObjectLockSet { get; set; } + + public NewMultipartUploadArgs WithObjectLockMode(ObjectRetentionMode mode) + { + ObjectLockSet = true; + ObjectLockRetentionMode = mode; + return this; + } + + public NewMultipartUploadArgs WithObjectLockRetentionDate(DateTime untilDate) + { + ObjectLockSet = true; + RetentionUntilDate = new DateTime(untilDate.Year, untilDate.Month, untilDate.Day, + untilDate.Hour, untilDate.Minute, untilDate.Second, untilDate.Kind); + return this; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("uploads", ""); + if (ObjectLockSet) + { + if (!RetentionUntilDate.Equals(default)) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-object-lock-retain-until-date", + Utils.To8601String(RetentionUntilDate)); + + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-object-lock-mode", + ObjectLockRetentionMode == ObjectRetentionMode.GOVERNANCE ? "GOVERNANCE" : "COMPLIANCE"); + } + + requestMessageBuilder.AddOrUpdateHeaderParameter("content-type", ContentType); + + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/NewMultipartUploadCopyArgs.cs b/LibExternal/Minio/DataModel/Args/NewMultipartUploadCopyArgs.cs new file mode 100644 index 0000000..b065215 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/NewMultipartUploadCopyArgs.cs @@ -0,0 +1,152 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.DataModel.ObjectLock; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +internal class NewMultipartUploadCopyArgs : NewMultipartUploadArgs +{ + internal bool ReplaceMetadataDirective { get; set; } + internal bool ReplaceTagsDirective { get; set; } + internal string StorageClass { get; set; } + internal ObjectStat SourceObjectInfo { get; set; } + internal CopySourceObjectArgs SourceObject { get; set; } + + internal override void Validate() + { + base.Validate(); + if (SourceObjectInfo is null || SourceObject is null) + throw new InvalidOperationException(nameof(SourceObjectInfo) + " and " + nameof(SourceObject) + + " need to be initialized for a NewMultipartUpload operation to work."); + + Populate(); + } + + private void Populate() + { + //Concat as Headers may have byte range info .etc. + if (!ReplaceMetadataDirective && SourceObjectInfo.MetaData?.Count > 0) + Headers = SourceObjectInfo.MetaData.Concat(Headers).GroupBy(item => item.Key, StringComparer.Ordinal) + .ToDictionary(item => item.Key, item => item.First().Value, StringComparer.Ordinal); + else if (ReplaceMetadataDirective) Headers ??= new Dictionary(StringComparer.Ordinal); + if (Headers is not null) + { + var newKVList = new List>(); + foreach (var item in Headers) + { + var key = item.Key; + if (!OperationsUtil.IsSupportedHeader(item.Key) && + !item.Key.StartsWith("x-amz-meta", StringComparison.OrdinalIgnoreCase) && + !OperationsUtil.IsSSEHeader(key)) + newKVList.Add(new Tuple("x-amz-meta-" + key.ToLowerInvariant(), item.Value)); + } + + foreach (var item in newKVList) Headers[item.Item1] = item.Item2; + } + } + + public new NewMultipartUploadCopyArgs WithObjectLockMode(ObjectRetentionMode mode) + { + _ = base.WithObjectLockMode(mode); + return this; + } + + public new NewMultipartUploadCopyArgs WithHeaders(IDictionary headers) + { + _ = base.WithHeaders(headers); + return this; + } + + public new NewMultipartUploadCopyArgs WithObjectLockRetentionDate(DateTime untilDate) + { + _ = base.WithObjectLockRetentionDate(untilDate); + return this; + } + + internal NewMultipartUploadCopyArgs WithStorageClass(string storageClass) + { + StorageClass = storageClass; + return this; + } + + internal NewMultipartUploadCopyArgs WithReplaceMetadataDirective(bool replace) + { + ReplaceMetadataDirective = replace; + return this; + } + + internal NewMultipartUploadCopyArgs WithReplaceTagsDirective(bool replace) + { + ReplaceTagsDirective = replace; + return this; + } + + public NewMultipartUploadCopyArgs WithSourceObjectInfo(ObjectStat stat) + { + SourceObjectInfo = stat; + return this; + } + + public NewMultipartUploadCopyArgs WithCopyObjectSource(CopySourceObjectArgs cs) + { + if (cs is null) + throw new InvalidOperationException("The copy source object needed for copy operation is not initialized."); + + SourceObject ??= new CopySourceObjectArgs(); + SourceObject.RequestMethod = HttpMethod.Put; + SourceObject.BucketName = cs.BucketName; + SourceObject.ObjectName = cs.ObjectName; + SourceObject.VersionId = cs.VersionId; + SourceObject.SSE = cs.SSE; + SourceObject.Headers = cs.Headers; + SourceObject.MatchETag = cs.MatchETag; + SourceObject.ModifiedSince = cs.ModifiedSince; + SourceObject.NotMatchETag = cs.NotMatchETag; + SourceObject.UnModifiedSince = cs.UnModifiedSince; + SourceObject.CopySourceObjectPath = $"{cs.BucketName}/{Utils.UrlEncode(cs.ObjectName)}"; + SourceObject.CopyOperationConditions = cs.CopyOperationConditions?.Clone(); + return this; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("uploads", ""); + if (ObjectTags?.TaggingSet?.Tag.Count > 0) + { + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-tagging", ObjectTags.GetTagString()); + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-tagging-directive", + ReplaceTagsDirective ? "REPLACE" : "COPY"); + } + + if (ReplaceMetadataDirective) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-metadata-directive", "REPLACE"); + if (!string.IsNullOrWhiteSpace(StorageClass)) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-storage-class", StorageClass); + if (ObjectLockSet) + { + if (!RetentionUntilDate.Equals(default)) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-object-lock-retain-until-date", + Utils.To8601String(RetentionUntilDate)); + + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-object-lock-mode", + ObjectLockRetentionMode == ObjectRetentionMode.GOVERNANCE ? "GOVERNANCE" : "COMPLIANCE"); + } + + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/NewMultipartUploadPutArgs.cs b/LibExternal/Minio/DataModel/Args/NewMultipartUploadPutArgs.cs new file mode 100644 index 0000000..8164e52 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/NewMultipartUploadPutArgs.cs @@ -0,0 +1,32 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +internal class NewMultipartUploadPutArgs : NewMultipartUploadArgs +{ + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("uploads", ""); + + if (ObjectTags?.TaggingSet?.Tag.Count > 0) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-tagging", ObjectTags.GetTagString()); + + requestMessageBuilder.AddOrUpdateHeaderParameter("content-type", ContentType); + + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/ObjectArgs.cs b/LibExternal/Minio/DataModel/Args/ObjectArgs.cs new file mode 100644 index 0000000..c09bdc6 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/ObjectArgs.cs @@ -0,0 +1,46 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.Helper; + +namespace Minio.DataModel.Args; + +public abstract class ObjectArgs : BucketArgs + where T : ObjectArgs +{ + protected const string S3ZipExtractKey = "X-Minio-Extract"; + + internal string ObjectName { get; set; } + internal ReadOnlyMemory RequestBody { get; set; } + + public T WithObject(string obj) + { + ObjectName = obj; + return (T)this; + } + + public T WithRequestBody(ReadOnlyMemory data) + { + RequestBody = data; + return (T)this; + } + + internal override void Validate() + { + base.Validate(); + Utils.ValidateObjectName(ObjectName); + } +} diff --git a/LibExternal/Minio/DataModel/Args/ObjectConditionalQueryArgs.cs b/LibExternal/Minio/DataModel/Args/ObjectConditionalQueryArgs.cs new file mode 100644 index 0000000..ea17fdf --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/ObjectConditionalQueryArgs.cs @@ -0,0 +1,79 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.Helper; + +namespace Minio.DataModel.Args; + +public abstract class ObjectConditionalQueryArgs : ObjectVersionArgs + where T : ObjectConditionalQueryArgs +{ + internal string MatchETag { get; set; } + internal string NotMatchETag { get; set; } + internal DateTime ModifiedSince { get; set; } + internal DateTime UnModifiedSince { get; set; } + + internal override void Validate() + { + base.Validate(); + if (!string.IsNullOrEmpty(MatchETag) && !string.IsNullOrEmpty(NotMatchETag)) + throw new InvalidOperationException("Cannot set both " + nameof(MatchETag) + " and " + + nameof(NotMatchETag) + " for query."); + + if (ModifiedSince != default && + UnModifiedSince != default) + throw new InvalidOperationException("Cannot set both " + nameof(ModifiedSince) + " and " + + nameof(UnModifiedSince) + " for query."); + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + if (!string.IsNullOrEmpty(MatchETag)) requestMessageBuilder.AddOrUpdateHeaderParameter("If-Match", MatchETag); + if (!string.IsNullOrEmpty(NotMatchETag)) + requestMessageBuilder.AddOrUpdateHeaderParameter("If-None-Match", NotMatchETag); + if (ModifiedSince != default) + requestMessageBuilder.AddOrUpdateHeaderParameter("If-Modified-Since", Utils.To8601String(ModifiedSince)); + if (UnModifiedSince != default) + requestMessageBuilder.AddOrUpdateHeaderParameter("If-Unmodified-Since", + Utils.To8601String(UnModifiedSince)); + + return requestMessageBuilder; + } + + public T WithMatchETag(string etag) + { + MatchETag = etag; + return (T)this; + } + + public T WithNotMatchETag(string etag) + { + NotMatchETag = etag; + return (T)this; + } + + public T WithModifiedSince(DateTime d) + { + ModifiedSince = new DateTime(d.Year, d.Month, d.Day, 0, 0, 0); + return (T)this; + } + + public T WithUnModifiedSince(DateTime d) + { + UnModifiedSince = new DateTime(d.Year, d.Month, d.Day, 0, 0, 0); + return (T)this; + } +} diff --git a/LibExternal/Minio/DataModel/Args/ObjectVersionArgs.cs b/LibExternal/Minio/DataModel/Args/ObjectVersionArgs.cs new file mode 100644 index 0000000..da1ee42 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/ObjectVersionArgs.cs @@ -0,0 +1,29 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public abstract class ObjectVersionArgs : EncryptionArgs + where T : ObjectVersionArgs +{ + internal string VersionId { get; set; } + + public T WithVersionId(string vid) + { + VersionId = vid; + return (T)this; + } +} diff --git a/LibExternal/Minio/DataModel/Args/ObjectWriteArgs.cs b/LibExternal/Minio/DataModel/Args/ObjectWriteArgs.cs new file mode 100644 index 0000000..da8ce8a --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/ObjectWriteArgs.cs @@ -0,0 +1,54 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.DataModel.ObjectLock; +using Minio.DataModel.Tags; + +namespace Minio.DataModel.Args; + +public abstract class ObjectWriteArgs : ObjectConditionalQueryArgs + where T : ObjectWriteArgs +{ + internal Tagging ObjectTags { get; set; } + internal ObjectRetentionConfiguration Retention { get; set; } + internal bool? LegalHoldEnabled { get; set; } + internal string ContentType { get; set; } + + public T WithTagging(Tagging tagging) + { + ObjectTags = tagging; + return (T)this; + } + + public T WithContentType(string type) + { + ContentType = string.IsNullOrWhiteSpace(type) ? "application/octet-stream" : type; + if (!Headers.ContainsKey("Content-Type")) Headers["Content-Type"] = type; + return (T)this; + } + + public T WithRetentionConfiguration(ObjectRetentionConfiguration retentionConfiguration) + { + Retention = retentionConfiguration; + return (T)this; + } + + public T WithLegalHold(bool? legalHold) + { + LegalHoldEnabled = legalHold; + return (T)this; + } +} diff --git a/LibExternal/Minio/DataModel/Args/PresignedGetObjectArgs.cs b/LibExternal/Minio/DataModel/Args/PresignedGetObjectArgs.cs new file mode 100644 index 0000000..872e8ae --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/PresignedGetObjectArgs.cs @@ -0,0 +1,51 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.Exceptions; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +public class PresignedGetObjectArgs : ObjectArgs +{ + public PresignedGetObjectArgs() + { + RequestMethod = HttpMethod.Get; + } + + internal int Expiry { get; set; } + internal DateTime? RequestDate { get; set; } + + internal override void Validate() + { + base.Validate(); + if (!Utils.IsValidExpiry(Expiry)) + throw new InvalidExpiryRangeException("expiry range should be between 1 and " + + Constants.DefaultExpiryTime); + } + + public PresignedGetObjectArgs WithExpiry(int expiry) + { + Expiry = expiry; + return this; + } + + public PresignedGetObjectArgs WithRequestDate(DateTime? d) + { + RequestDate = d; + return this; + } +} diff --git a/LibExternal/Minio/DataModel/Args/PresignedPostPolicyArgs.cs b/LibExternal/Minio/DataModel/Args/PresignedPostPolicyArgs.cs new file mode 100644 index 0000000..15c017e --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/PresignedPostPolicyArgs.cs @@ -0,0 +1,114 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Globalization; +using Minio.Exceptions; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +public class PresignedPostPolicyArgs : ObjectArgs +{ + internal PostPolicy Policy { get; set; } + internal DateTime Expiration { get; set; } + + internal string Region { get; set; } + + protected new void Validate() + { + var checkPolicy = false; + try + { + Utils.ValidateBucketName(BucketName); + Utils.ValidateObjectName(ObjectName); + } + catch (Exception ex) when (ex is InvalidBucketNameException || ex is InvalidObjectNameException) + { + checkPolicy = true; + } + + if (checkPolicy) + { + if (!Policy.IsBucketSet()) + throw new InvalidOperationException("For the " + nameof(Policy) + " bucket should be set"); + + if (!Policy.IsKeySet()) + throw new InvalidOperationException("For the " + nameof(Policy) + " key should be set"); + + if (!Policy.IsExpirationSet()) + throw new InvalidOperationException("For the " + nameof(Policy) + " expiration should be set"); + BucketName = Policy.Bucket; + ObjectName = Policy.Key; + } + + if (string.IsNullOrEmpty(Expiration.ToString(CultureInfo.InvariantCulture))) + throw new InvalidOperationException("For the " + nameof(Policy) + " expiration should be set"); + } + + public PresignedPostPolicyArgs WithExpiration(DateTime ex) + { + Expiration = ex; + return this; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + return requestMessageBuilder; + } + + internal PresignedPostPolicyArgs WithRegion(string region) + { + Region = region; + return this; + } + + internal PresignedPostPolicyArgs WithSessionToken(string sessionToken) + { + Policy.SetSessionToken(sessionToken); + return this; + } + + internal PresignedPostPolicyArgs WithDate(DateTime date) + { + Policy.SetDate(date); + return this; + } + + internal PresignedPostPolicyArgs WithCredential(string credential) + { + Policy.SetCredential(credential); + return this; + } + + internal PresignedPostPolicyArgs WithAlgorithm(string algorithm) + { + Policy.SetAlgorithm(algorithm); + return this; + } + + public PresignedPostPolicyArgs WithPolicy(PostPolicy policy) + { + if (policy is null) + throw new ArgumentNullException(nameof(policy)); + + Policy = policy; + if (policy.Expiration != DateTime.MinValue) + // policy.expiration has an assigned value + Expiration = policy.Expiration; + + return this; + } +} diff --git a/LibExternal/Minio/DataModel/Args/PresignedPutObjectArgs.cs b/LibExternal/Minio/DataModel/Args/PresignedPutObjectArgs.cs new file mode 100644 index 0000000..4611dea --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/PresignedPutObjectArgs.cs @@ -0,0 +1,49 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.Exceptions; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +public class PresignedPutObjectArgs : ObjectArgs +{ + public PresignedPutObjectArgs() + { + RequestMethod = HttpMethod.Put; + } + + internal int Expiry { get; set; } + + protected new void Validate() + { + base.Validate(); + if (!Utils.IsValidExpiry(Expiry)) + throw new InvalidExpiryRangeException("Expiry range should be between 1 seconds and " + + Constants.DefaultExpiryTime + " seconds"); + } + + public PresignedPutObjectArgs WithExpiry(int ex) + { + Expiry = ex; + return this; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/PutObjectArgs.cs b/LibExternal/Minio/DataModel/Args/PutObjectArgs.cs new file mode 100644 index 0000000..e029b06 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/PutObjectArgs.cs @@ -0,0 +1,193 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Security.Cryptography; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +public class PutObjectArgs : ObjectWriteArgs +{ + public PutObjectArgs() + { + RequestMethod = HttpMethod.Put; + RequestBody = null; + ObjectStreamData = null; + PartNumber = 0; + ContentType = "application/octet-stream"; + } + + internal PutObjectArgs(PutObjectPartArgs args) + { + RequestMethod = HttpMethod.Put; + BucketName = args.BucketName; + ContentType = args.ContentType ?? "application/octet-stream"; + FileName = args.FileName; + Headers = args.Headers; + ObjectName = args.ObjectName; + ObjectSize = args.ObjectSize; + PartNumber = args.PartNumber; + SSE = args.SSE; + UploadId = args.UploadId; + } + + internal string UploadId { get; private set; } + internal int PartNumber { get; set; } + internal string FileName { get; set; } + internal long ObjectSize { get; set; } + internal Stream ObjectStreamData { get; set; } + internal IProgress Progress { get; set; } + + internal override void Validate() + { + base.Validate(); + // Check atleast one of filename or stream are initialized + if (string.IsNullOrWhiteSpace(FileName) && ObjectStreamData is null) + throw new InvalidOperationException("One of " + nameof(FileName) + " or " + nameof(ObjectStreamData) + + " must be set."); + + if (PartNumber < 0) + throw new InvalidDataException("Invalid Part number value. Cannot be less than 0"); + // Check if only one of filename or stream are initialized + if (!string.IsNullOrWhiteSpace(FileName) && ObjectStreamData is not null) + throw new InvalidOperationException("Only one of " + nameof(FileName) + " or " + nameof(ObjectStreamData) + + " should be set."); + + if (!string.IsNullOrWhiteSpace(FileName)) Utils.ValidateFile(FileName); + // Check object size when using stream data + if (ObjectStreamData is not null && ObjectSize == 0) + throw new InvalidOperationException($"{nameof(ObjectSize)} must be set"); + Populate(); + } + + private void Populate() + { + if (!string.IsNullOrWhiteSpace(FileName)) + { + var fileInfo = new FileInfo(FileName); + ObjectSize = fileInfo.Length; + ObjectStreamData = new FileStream(FileName, FileMode.Open, FileAccess.Read); + } + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder = base.BuildRequest(requestMessageBuilder); + if (string.IsNullOrWhiteSpace(ContentType)) ContentType = "application/octet-stream"; + if (!Headers.ContainsKey("Content-Type")) Headers["Content-Type"] = ContentType; + + requestMessageBuilder.AddOrUpdateHeaderParameter("Content-Type", Headers["Content-Type"]); + if (!string.IsNullOrWhiteSpace(UploadId) && PartNumber > 0) + { + requestMessageBuilder.AddQueryParameter("uploadId", $"{UploadId}"); + requestMessageBuilder.AddQueryParameter("partNumber", $"{PartNumber}"); + } + + if (ObjectTags?.TaggingSet?.Tag.Count > 0) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-tagging", ObjectTags.GetTagString()); + + if (Retention is not null) + { + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-object-lock-retain-until-date", + Retention.RetainUntilDate); + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-object-lock-mode", Retention.Mode.ToString()); + requestMessageBuilder.AddOrUpdateHeaderParameter("Content-Md5", + Utils.GetMD5SumStr(RequestBody.Span)); + } + + if (LegalHoldEnabled is not null) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-object-lock-legal-hold", + LegalHoldEnabled == true ? "ON" : "OFF"); + + if (!RequestBody.IsEmpty) + { +#if NETSTANDARD + using var sha = SHA256.Create(); + var hash + = sha.ComputeHash(RequestBody.ToArray()); +#else + var hash = SHA256.HashData(RequestBody.Span); +#endif + var hex = BitConverter.ToString(hash).Replace("-", string.Empty, StringComparison.OrdinalIgnoreCase) + .ToLowerInvariant(); + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-content-sha256", hex); + requestMessageBuilder.SetBody(RequestBody); + } + + return requestMessageBuilder; + } + + public override PutObjectArgs WithHeaders(IDictionary headers) + { + Headers ??= new Dictionary(StringComparer.OrdinalIgnoreCase); + if (headers is not null) + foreach (var p in headers) + { + var key = p.Key; + if (!OperationsUtil.IsSupportedHeader(p.Key) && + !p.Key.StartsWith("x-amz-meta-", StringComparison.OrdinalIgnoreCase) && + !OperationsUtil.IsSSEHeader(p.Key)) + { + key = "x-amz-meta-" + key.ToLowerInvariant(); + _ = Headers.Remove(p.Key); + } + + Headers[key] = p.Value; + if (string.Equals(key, "Content-Type", StringComparison.OrdinalIgnoreCase)) + ContentType = p.Value; + } + + if (string.IsNullOrWhiteSpace(ContentType)) ContentType = "application/octet-stream"; + if (!Headers.ContainsKey("Content-Type")) Headers["Content-Type"] = ContentType; + return this; + } + + internal PutObjectArgs WithUploadId(string id = null) + { + UploadId = id; + return this; + } + + internal PutObjectArgs WithPartNumber(int num) + { + PartNumber = num; + return this; + } + + public PutObjectArgs WithFileName(string file) + { + FileName = file; + return this; + } + + public PutObjectArgs WithObjectSize(long size) + { + ObjectSize = size; + return this; + } + + public PutObjectArgs WithStreamData(Stream data) + { + ObjectStreamData = data; + return this; + } + + public PutObjectArgs WithProgress(IProgress progress) + { + Progress = progress; + return this; + } +} diff --git a/LibExternal/Minio/DataModel/Args/PutObjectPartArgs.cs b/LibExternal/Minio/DataModel/Args/PutObjectPartArgs.cs new file mode 100644 index 0000000..6479422 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/PutObjectPartArgs.cs @@ -0,0 +1,84 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.Helper; + +namespace Minio.DataModel.Args; + +internal class PutObjectPartArgs : PutObjectArgs +{ + public PutObjectPartArgs() + { + RequestMethod = HttpMethod.Put; + } + + internal override void Validate() + { + base.Validate(); + if (string.IsNullOrWhiteSpace(UploadId)) + throw new InvalidOperationException(nameof(UploadId) + " not assigned for PutObjectPart operation."); + } + + public new PutObjectPartArgs WithBucket(string bkt) + { + return (PutObjectPartArgs)base.WithBucket(bkt); + } + + public new PutObjectPartArgs WithObject(string obj) + { + return (PutObjectPartArgs)base.WithObject(obj); + } + + public new PutObjectPartArgs WithObjectSize(long size) + { + return (PutObjectPartArgs)base.WithObjectSize(size); + } + + public new PutObjectPartArgs WithHeaders(IDictionary hdr) + { + return (PutObjectPartArgs)base.WithHeaders(hdr); + } + + public PutObjectPartArgs WithRequestBody(object data) + { + return (PutObjectPartArgs)base.WithRequestBody(Utils.ObjectToByteArray(data)); + } + + public new PutObjectPartArgs WithStreamData(Stream data) + { + return (PutObjectPartArgs)base.WithStreamData(data); + } + + public new PutObjectPartArgs WithContentType(string type) + { + return (PutObjectPartArgs)base.WithContentType(type); + } + + public new PutObjectPartArgs WithUploadId(string id) + { + return (PutObjectPartArgs)base.WithUploadId(id); + } + + public new PutObjectPartArgs WithProgress(IProgress progress) + { + return (PutObjectPartArgs)base.WithProgress(progress); + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/RemoveAllBucketNotificationsArgs.cs b/LibExternal/Minio/DataModel/Args/RemoveAllBucketNotificationsArgs.cs new file mode 100644 index 0000000..8d28d78 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/RemoveAllBucketNotificationsArgs.cs @@ -0,0 +1,42 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Text; +using Minio.DataModel.Notification; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +public class RemoveAllBucketNotificationsArgs : BucketArgs +{ + public RemoveAllBucketNotificationsArgs() + { + RequestMethod = HttpMethod.Put; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("notification", ""); + var bucketNotificationConfiguration = new BucketNotification(); + var body = Utils.MarshalXML(bucketNotificationConfiguration, "http://s3.amazonaws.com/doc/2006-03-01/"); + // Convert string to a byte array + ReadOnlyMemory bodyInBytes = Encoding.ASCII.GetBytes(body); + requestMessageBuilder.BodyParameters.Add("content-type", "text/xml"); + requestMessageBuilder.SetBody(bodyInBytes); + + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/RemoveBucketArgs.cs b/LibExternal/Minio/DataModel/Args/RemoveBucketArgs.cs new file mode 100644 index 0000000..a6ad437 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/RemoveBucketArgs.cs @@ -0,0 +1,32 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class RemoveBucketArgs : BucketArgs +{ + public RemoveBucketArgs() + { + RequestMethod = HttpMethod.Delete; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + if (Headers.TryGetValue(BucketForceDeleteKey, out var value)) + requestMessageBuilder.AddHeaderParameter(BucketForceDeleteKey, value); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/RemoveBucketEncryptionArgs.cs b/LibExternal/Minio/DataModel/Args/RemoveBucketEncryptionArgs.cs new file mode 100644 index 0000000..39200bd --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/RemoveBucketEncryptionArgs.cs @@ -0,0 +1,31 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class RemoveBucketEncryptionArgs : BucketArgs +{ + public RemoveBucketEncryptionArgs() + { + RequestMethod = HttpMethod.Delete; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("encryption", ""); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/RemoveBucketLifecycleArgs.cs b/LibExternal/Minio/DataModel/Args/RemoveBucketLifecycleArgs.cs new file mode 100644 index 0000000..e0d114d --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/RemoveBucketLifecycleArgs.cs @@ -0,0 +1,31 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class RemoveBucketLifecycleArgs : BucketArgs +{ + public RemoveBucketLifecycleArgs() + { + RequestMethod = HttpMethod.Delete; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("lifecycle", ""); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/RemoveBucketReplicationArgs.cs b/LibExternal/Minio/DataModel/Args/RemoveBucketReplicationArgs.cs new file mode 100644 index 0000000..ac007e4 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/RemoveBucketReplicationArgs.cs @@ -0,0 +1,31 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class RemoveBucketReplicationArgs : BucketArgs +{ + public RemoveBucketReplicationArgs() + { + RequestMethod = HttpMethod.Delete; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("replication", ""); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/RemoveBucketTagsArgs.cs b/LibExternal/Minio/DataModel/Args/RemoveBucketTagsArgs.cs new file mode 100644 index 0000000..b8dcf3f --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/RemoveBucketTagsArgs.cs @@ -0,0 +1,31 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class RemoveBucketTagsArgs : BucketArgs +{ + public RemoveBucketTagsArgs() + { + RequestMethod = HttpMethod.Delete; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("tagging", ""); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/RemoveIncompleteUploadArgs.cs b/LibExternal/Minio/DataModel/Args/RemoveIncompleteUploadArgs.cs new file mode 100644 index 0000000..7068691 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/RemoveIncompleteUploadArgs.cs @@ -0,0 +1,25 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class RemoveIncompleteUploadArgs : EncryptionArgs +{ + public RemoveIncompleteUploadArgs() + { + RequestMethod = HttpMethod.Delete; + } +} diff --git a/LibExternal/Minio/DataModel/Args/RemoveObjectArgs.cs b/LibExternal/Minio/DataModel/Args/RemoveObjectArgs.cs new file mode 100644 index 0000000..ae620d0 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/RemoveObjectArgs.cs @@ -0,0 +1,54 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class RemoveObjectArgs : ObjectArgs +{ + public RemoveObjectArgs() + { + RequestMethod = HttpMethod.Delete; + BypassGovernanceMode = null; + } + + internal string VersionId { get; private set; } + internal bool? BypassGovernanceMode { get; private set; } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + if (!string.IsNullOrEmpty(VersionId)) + { + requestMessageBuilder.AddQueryParameter("versionId", $"{VersionId}"); + if (BypassGovernanceMode == true) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-bypass-governance-retention", + BypassGovernanceMode.Value.ToString()); + } + + return requestMessageBuilder; + } + + public RemoveObjectArgs WithVersionId(string ver) + { + VersionId = ver; + return this; + } + + public RemoveObjectArgs WithBypassGovernanceMode(bool? mode) + { + BypassGovernanceMode = mode; + return this; + } +} diff --git a/LibExternal/Minio/DataModel/Args/RemoveObjectLockConfigurationArgs.cs b/LibExternal/Minio/DataModel/Args/RemoveObjectLockConfigurationArgs.cs new file mode 100644 index 0000000..a355767 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/RemoveObjectLockConfigurationArgs.cs @@ -0,0 +1,39 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Text; +using Minio.DataModel.ObjectLock; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +public class RemoveObjectLockConfigurationArgs : BucketArgs +{ + public RemoveObjectLockConfigurationArgs() + { + RequestMethod = HttpMethod.Put; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("object-lock", ""); + var body = Utils.MarshalXML(new ObjectLockConfiguration(), "http://s3.amazonaws.com/doc/2006-03-01/"); + requestMessageBuilder.AddXmlBody(body); + requestMessageBuilder.AddOrUpdateHeaderParameter("Content-Md5", + Utils.GetMD5SumStr(Encoding.UTF8.GetBytes(body))); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/RemoveObjectTagsArgs.cs b/LibExternal/Minio/DataModel/Args/RemoveObjectTagsArgs.cs new file mode 100644 index 0000000..321135b --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/RemoveObjectTagsArgs.cs @@ -0,0 +1,32 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class RemoveObjectTagsArgs : ObjectVersionArgs +{ + public RemoveObjectTagsArgs() + { + RequestMethod = HttpMethod.Delete; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("tagging", ""); + if (!string.IsNullOrEmpty(VersionId)) requestMessageBuilder.AddQueryParameter("versionId", VersionId); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/RemoveObjectsArgs.cs b/LibExternal/Minio/DataModel/Args/RemoveObjectsArgs.cs new file mode 100644 index 0000000..9046d23 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/RemoveObjectsArgs.cs @@ -0,0 +1,125 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Globalization; +using System.Text; +using System.Xml.Linq; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +public class RemoveObjectsArgs : ObjectArgs +{ + public RemoveObjectsArgs() + { + ObjectName = null; + ObjectNames = new List(); + ObjectNamesVersions = new List>(); + RequestMethod = HttpMethod.Post; + } + + internal IList ObjectNames { get; private set; } + + // Each element in the list is a Tuple. Each Tuple has an Object name & the version ID. + internal List> ObjectNamesVersions { get; } + + public RemoveObjectsArgs WithObjectAndVersions(string objectName, IList versions) + { + if (versions is null) + throw new ArgumentNullException(nameof(versions)); + + foreach (var vid in versions) + ObjectNamesVersions.Add(new Tuple(objectName, vid)); + return this; + } + + // Tuple>. Tuple object name -> List of Version IDs. + public RemoveObjectsArgs WithObjectsVersions(IList>> objectsVersionsList) + { + if (objectsVersionsList is null) + throw new ArgumentNullException(nameof(objectsVersionsList)); + + foreach (var objVersions in objectsVersionsList) + foreach (var vid in objVersions.Item2) + ObjectNamesVersions.Add(new Tuple(objVersions.Item1, vid)); + + return this; + } + + public RemoveObjectsArgs WithObjectsVersions(IList> objectVersions) + { + ObjectNamesVersions.AddRange(objectVersions); + return this; + } + + public RemoveObjectsArgs WithObjects(IList names) + { + ObjectNames = names; + return this; + } + + internal override void Validate() + { + // Skip object name validation. + Utils.ValidateBucketName(BucketName); + if (!string.IsNullOrEmpty(ObjectName)) + throw new InvalidOperationException(nameof(ObjectName) + " is set. Please use " + nameof(WithObjects) + + "or " + + nameof(WithObjectsVersions) + " method to set objects to be deleted."); + + if ((ObjectNames is null && ObjectNamesVersions is null) || + (ObjectNames.Count == 0 && ObjectNamesVersions.Count == 0)) + throw new InvalidOperationException( + "Please assign list of object names or object names and version IDs to remove using method(s) " + + nameof(WithObjects) + " " + nameof(WithObjectsVersions)); + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + var objects = new List(); + requestMessageBuilder.AddQueryParameter("delete", ""); + XElement deleteObjectsRequest; + if (ObjectNamesVersions.Count > 0) + { + // Object(s) & multiple versions + foreach (var objTuple in ObjectNamesVersions) + objects.Add(new XElement("Object", + new XElement("Key", objTuple.Item1), + new XElement("VersionId", objTuple.Item2))); + + deleteObjectsRequest = new XElement("Delete", objects, + new XElement("Quiet", true)); + requestMessageBuilder.AddXmlBody(Convert.ToString(deleteObjectsRequest, CultureInfo.InvariantCulture)); + } + else + { + // Multiple Objects + foreach (var obj in ObjectNames) + objects.Add(new XElement("Object", + new XElement("Key", obj))); + + deleteObjectsRequest = new XElement("Delete", objects, + new XElement("Quiet", true)); + requestMessageBuilder.AddXmlBody(Convert.ToString(deleteObjectsRequest, CultureInfo.InvariantCulture)); + } + + requestMessageBuilder.AddOrUpdateHeaderParameter("Content-Md5", + Utils.GetMD5SumStr( + Encoding.UTF8.GetBytes(Convert.ToString(deleteObjectsRequest, CultureInfo.InvariantCulture)))); + + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/RemovePolicyArgs.cs b/LibExternal/Minio/DataModel/Args/RemovePolicyArgs.cs new file mode 100644 index 0000000..534b0a9 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/RemovePolicyArgs.cs @@ -0,0 +1,31 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class RemovePolicyArgs : BucketArgs +{ + public RemovePolicyArgs() + { + RequestMethod = HttpMethod.Delete; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("policy", ""); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/RemoveUploadArgs.cs b/LibExternal/Minio/DataModel/Args/RemoveUploadArgs.cs new file mode 100644 index 0000000..036ef8b --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/RemoveUploadArgs.cs @@ -0,0 +1,47 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public class RemoveUploadArgs : EncryptionArgs +{ + public RemoveUploadArgs() + { + RequestMethod = HttpMethod.Delete; + } + + internal string UploadId { get; private set; } + + public RemoveUploadArgs WithUploadId(string id) + { + UploadId = id; + return this; + } + + internal override void Validate() + { + base.Validate(); + if (string.IsNullOrEmpty(UploadId)) + throw new InvalidOperationException(nameof(UploadId) + + " cannot be empty. Please assign a valid upload ID to remove."); + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("uploadId", $"{UploadId}"); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/RequestArgs.cs b/LibExternal/Minio/DataModel/Args/RequestArgs.cs new file mode 100644 index 0000000..3dbe376 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/RequestArgs.cs @@ -0,0 +1,31 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Args; + +public abstract class RequestArgs + +{ + // RequestMethod will be the HTTP Method for request variable, + // which is of type HttpRequestMessage. + // Will be one of the types: - HEAD, GET, PUT, DELETE. etc. + internal HttpMethod RequestMethod { get; set; } + + internal virtual HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/SelectObjectContentArgs.cs b/LibExternal/Minio/DataModel/Args/SelectObjectContentArgs.cs new file mode 100644 index 0000000..7628bfc --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/SelectObjectContentArgs.cs @@ -0,0 +1,92 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Text; +using Minio.DataModel.Select; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +public class SelectObjectContentArgs : EncryptionArgs +{ + private readonly SelectObjectOptions SelectOptions; + + public SelectObjectContentArgs() + { + RequestMethod = HttpMethod.Post; + SelectOptions = new SelectObjectOptions(); + } + + internal override void Validate() + { + base.Validate(); + if (string.IsNullOrEmpty(SelectOptions.Expression)) + throw new InvalidOperationException("The Expression " + nameof(SelectOptions.Expression) + + " for Select Object Content cannot be empty."); + + if (SelectOptions.InputSerialization is null || SelectOptions.OutputSerialization is null) + throw new InvalidOperationException( + "The Input/Output serialization members for SelectObjectContentArgs should be initialized " + + nameof(SelectOptions.InputSerialization) + " " + nameof(SelectOptions.OutputSerialization)); + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("select", ""); + requestMessageBuilder.AddQueryParameter("select-type", "2"); + + if (RequestBody.IsEmpty) + { + RequestBody = Encoding.UTF8.GetBytes(SelectOptions.MarshalXML()); + requestMessageBuilder.SetBody(RequestBody); + } + + requestMessageBuilder.AddOrUpdateHeaderParameter("Content-Md5", + Utils.GetMD5SumStr(RequestBody.Span)); + + return requestMessageBuilder; + } + + public SelectObjectContentArgs WithExpressionType(QueryExpressionType e) + { + SelectOptions.ExpressionType = e; + return this; + } + + public SelectObjectContentArgs WithQueryExpression(string expr) + { + SelectOptions.Expression = expr; + return this; + } + + public SelectObjectContentArgs WithInputSerialization(SelectObjectInputSerialization serialization) + { + SelectOptions.InputSerialization = serialization; + return this; + } + + public SelectObjectContentArgs WithOutputSerialization(SelectObjectOutputSerialization serialization) + { + SelectOptions.OutputSerialization = serialization; + return this; + } + + public SelectObjectContentArgs WithRequestProgress(RequestProgress requestProgress) + { + SelectOptions.RequestProgress = requestProgress; + return this; + } +} diff --git a/LibExternal/Minio/DataModel/Args/SetBucketEncryptionArgs.cs b/LibExternal/Minio/DataModel/Args/SetBucketEncryptionArgs.cs new file mode 100644 index 0000000..f7a3b2f --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/SetBucketEncryptionArgs.cs @@ -0,0 +1,63 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Text; +using Minio.DataModel.Encryption; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +public class SetBucketEncryptionArgs : BucketArgs +{ + public SetBucketEncryptionArgs() + { + RequestMethod = HttpMethod.Put; + } + + internal ServerSideEncryptionConfiguration EncryptionConfig { get; set; } + + public SetBucketEncryptionArgs WithEncryptionConfig(ServerSideEncryptionConfiguration config) + { + EncryptionConfig = config; + return this; + } + + public SetBucketEncryptionArgs WithAESConfig() + { + EncryptionConfig = ServerSideEncryptionConfiguration.GetSSEConfigurationWithS3Rule(); + return this; + } + + public SetBucketEncryptionArgs WithKMSConfig(string keyId = null) + { + EncryptionConfig = ServerSideEncryptionConfiguration.GetSSEConfigurationWithKMSRule(keyId); + return this; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + EncryptionConfig ??= ServerSideEncryptionConfiguration.GetSSEConfigurationWithS3Rule(); + + requestMessageBuilder.AddQueryParameter("encryption", ""); + var body = Utils.MarshalXML(EncryptionConfig, "http://s3.amazonaws.com/doc/2006-03-01/"); + // Convert string to a byte array + ReadOnlyMemory bodyInBytes = Encoding.ASCII.GetBytes(body); + requestMessageBuilder.BodyParameters.Add("content-type", "text/xml"); + requestMessageBuilder.SetBody(bodyInBytes); + + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/SetBucketLifecycleArgs.cs b/LibExternal/Minio/DataModel/Args/SetBucketLifecycleArgs.cs new file mode 100644 index 0000000..8030a06 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/SetBucketLifecycleArgs.cs @@ -0,0 +1,58 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Text; +using Minio.DataModel.ILM; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +public class SetBucketLifecycleArgs : BucketArgs +{ + public SetBucketLifecycleArgs() + { + RequestMethod = HttpMethod.Put; + } + + internal LifecycleConfiguration BucketLifecycle { get; private set; } + + public SetBucketLifecycleArgs WithLifecycleConfiguration(LifecycleConfiguration lc) + { + BucketLifecycle = lc; + return this; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("lifecycle", ""); + var body = BucketLifecycle.MarshalXML(); + // Convert string to a byte array + ReadOnlyMemory bodyInBytes = Encoding.ASCII.GetBytes(body); + requestMessageBuilder.BodyParameters.Add("content-type", "text/xml"); + requestMessageBuilder.SetBody(bodyInBytes); + requestMessageBuilder.AddOrUpdateHeaderParameter("Content-Md5", + Utils.GetMD5SumStr(bodyInBytes.Span)); + + return requestMessageBuilder; + } + + internal override void Validate() + { + base.Validate(); + if (BucketLifecycle is null || BucketLifecycle.Rules.Count == 0) + throw new InvalidOperationException("Unable to set empty Lifecycle configuration."); + } +} diff --git a/LibExternal/Minio/DataModel/Args/SetBucketNotificationsArgs.cs b/LibExternal/Minio/DataModel/Args/SetBucketNotificationsArgs.cs new file mode 100644 index 0000000..32c7fe0 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/SetBucketNotificationsArgs.cs @@ -0,0 +1,54 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Text; +using Minio.DataModel.Notification; +using Minio.Exceptions; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +public class SetBucketNotificationsArgs : BucketArgs +{ + public SetBucketNotificationsArgs() + { + RequestMethod = HttpMethod.Put; + } + + internal BucketNotification BucketNotificationConfiguration { private set; get; } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + if (BucketNotificationConfiguration is null) + throw new UnexpectedMinioException( + "Cannot BuildRequest for SetBucketNotificationsArgs. BucketNotification configuration not assigned"); + + requestMessageBuilder.AddQueryParameter("notification", ""); + var body = Utils.MarshalXML(BucketNotificationConfiguration, "http://s3.amazonaws.com/doc/2006-03-01/"); + // Convert string to a byte array + ReadOnlyMemory bodyInBytes = Encoding.ASCII.GetBytes(body); + requestMessageBuilder.BodyParameters.Add("content-type", "text/xml"); + requestMessageBuilder.SetBody(bodyInBytes); + + return requestMessageBuilder; + } + + public SetBucketNotificationsArgs WithBucketNotificationConfiguration(BucketNotification config) + { + BucketNotificationConfiguration = config; + return this; + } +} diff --git a/LibExternal/Minio/DataModel/Args/SetBucketReplicationArgs.cs b/LibExternal/Minio/DataModel/Args/SetBucketReplicationArgs.cs new file mode 100644 index 0000000..b8c57b0 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/SetBucketReplicationArgs.cs @@ -0,0 +1,48 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Text; +using Minio.DataModel.Replication; + +namespace Minio.DataModel.Args; + +public class SetBucketReplicationArgs : BucketArgs +{ + public SetBucketReplicationArgs() + { + RequestMethod = HttpMethod.Put; + } + + internal ReplicationConfiguration BucketReplication { get; private set; } + + public SetBucketReplicationArgs WithConfiguration(ReplicationConfiguration conf) + { + BucketReplication = conf; + return this; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("replication", ""); + var body = BucketReplication.MarshalXML(); + // Convert string to a byte array + ReadOnlyMemory bodyInBytes = Encoding.ASCII.GetBytes(body); + requestMessageBuilder.BodyParameters.Add("content-type", "text/xml"); + requestMessageBuilder.SetBody(bodyInBytes); + + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/SetBucketTagsArgs.cs b/LibExternal/Minio/DataModel/Args/SetBucketTagsArgs.cs new file mode 100644 index 0000000..081cfad --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/SetBucketTagsArgs.cs @@ -0,0 +1,60 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Text; +using Minio.DataModel.Tags; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +public class SetBucketTagsArgs : BucketArgs +{ + public SetBucketTagsArgs() + { + RequestMethod = HttpMethod.Put; + } + + internal Tagging BucketTags { get; private set; } + + public SetBucketTagsArgs WithTagging(Tagging tags) + { + if (tags is null) + throw new ArgumentNullException(nameof(tags)); + + BucketTags = Tagging.GetBucketTags(tags.Tags); + return this; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("tagging", ""); + var body = BucketTags.MarshalXML(); + + requestMessageBuilder.AddXmlBody(body); + requestMessageBuilder.AddOrUpdateHeaderParameter("Content-Md5", + Utils.GetMD5SumStr(Encoding.UTF8.GetBytes(body))); + + // + return requestMessageBuilder; + } + + internal override void Validate() + { + base.Validate(); + if (BucketTags is null || BucketTags.Tags.Count == 0) + throw new InvalidOperationException("Unable to set empty tags."); + } +} diff --git a/LibExternal/Minio/DataModel/Args/SetObjectLegalHoldArgs.cs b/LibExternal/Minio/DataModel/Args/SetObjectLegalHoldArgs.cs new file mode 100644 index 0000000..87dea91 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/SetObjectLegalHoldArgs.cs @@ -0,0 +1,50 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Text; +using Minio.DataModel.ObjectLock; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +public class SetObjectLegalHoldArgs : ObjectVersionArgs +{ + public SetObjectLegalHoldArgs() + { + RequestMethod = HttpMethod.Put; + LegalHoldON = false; + } + + internal bool LegalHoldON { get; private set; } + + public SetObjectLegalHoldArgs WithLegalHold(bool status) + { + LegalHoldON = status; + return this; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("legal-hold", ""); + if (!string.IsNullOrEmpty(VersionId)) requestMessageBuilder.AddQueryParameter("versionId", VersionId); + var config = new ObjectLegalHoldConfiguration(LegalHoldON); + var body = Utils.MarshalXML(config, "http://s3.amazonaws.com/doc/2006-03-01/"); + requestMessageBuilder.AddXmlBody(body); + requestMessageBuilder.AddOrUpdateHeaderParameter("Content-Md5", + Utils.GetMD5SumStr(Encoding.UTF8.GetBytes(body))); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/SetObjectLockConfigurationArgs.cs b/LibExternal/Minio/DataModel/Args/SetObjectLockConfigurationArgs.cs new file mode 100644 index 0000000..61a7e09 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/SetObjectLockConfigurationArgs.cs @@ -0,0 +1,64 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Text; +using Minio.DataModel.ObjectLock; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +public class SetObjectLockConfigurationArgs : BucketArgs +{ + public SetObjectLockConfigurationArgs() + { + RequestMethod = HttpMethod.Put; + } + + internal ObjectLockConfiguration LockConfiguration { set; get; } + + public SetObjectLockConfigurationArgs WithLockConfiguration(ObjectLockConfiguration config) + { + LockConfiguration = config; + return this; + } + + internal override void Validate() + { + base.Validate(); + if (LockConfiguration is null) + throw new InvalidOperationException("The lock configuration object " + nameof(LockConfiguration) + + " is not set. Please use " + nameof(WithLockConfiguration) + + " to set."); + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("object-lock", ""); + var body = Utils.MarshalXML(LockConfiguration, "http://s3.amazonaws.com/doc/2006-03-01/"); + // Convert string to a byte array + // byte[] bodyInBytes = Encoding.ASCII.GetBytes(body); + + // requestMessageBuilder.BodyParameters.Add("content-type", "text/xml"); + // requestMessageBuilder.SetBody(bodyInBytes); + // + // string body = utils.MarshalXML(config, "http://s3.amazonaws.com/doc/2006-03-01/"); + requestMessageBuilder.AddXmlBody(body); + requestMessageBuilder.AddOrUpdateHeaderParameter("Content-Md5", + Utils.GetMD5SumStr(Encoding.UTF8.GetBytes(body))); + // + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/SetObjectRetentionArgs.cs b/LibExternal/Minio/DataModel/Args/SetObjectRetentionArgs.cs new file mode 100644 index 0000000..dab2ff4 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/SetObjectRetentionArgs.cs @@ -0,0 +1,79 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Text; +using Minio.DataModel.ObjectLock; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +public class SetObjectRetentionArgs : ObjectVersionArgs +{ + public SetObjectRetentionArgs() + { + RequestMethod = HttpMethod.Put; + RetentionUntilDate = default; + Mode = ObjectRetentionMode.GOVERNANCE; + } + + internal bool BypassGovernanceMode { get; set; } + internal ObjectRetentionMode Mode { get; set; } + internal DateTime RetentionUntilDate { get; set; } + + internal override void Validate() + { + base.Validate(); + if (RetentionUntilDate.Equals(default)) + throw new InvalidOperationException("Retention Period is not set. Please set using " + + nameof(WithRetentionUntilDate) + "."); + + if (DateTime.Compare(RetentionUntilDate, DateTime.Now) <= 0) + throw new InvalidOperationException("Retention until date set using " + nameof(WithRetentionUntilDate) + + " needs to be in the future."); + } + + public SetObjectRetentionArgs WithBypassGovernanceMode(bool bypass = true) + { + BypassGovernanceMode = bypass; + return this; + } + + public SetObjectRetentionArgs WithRetentionMode(ObjectRetentionMode mode) + { + Mode = mode; + return this; + } + + public SetObjectRetentionArgs WithRetentionUntilDate(DateTime date) + { + RetentionUntilDate = date; + return this; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("retention", ""); + if (!string.IsNullOrEmpty(VersionId)) requestMessageBuilder.AddQueryParameter("versionId", VersionId); + if (BypassGovernanceMode) + requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-bypass-governance-retention", "true"); + var config = new ObjectRetentionConfiguration(RetentionUntilDate, Mode); + var body = Utils.MarshalXML(config, "http://s3.amazonaws.com/doc/2006-03-01/"); + requestMessageBuilder.AddXmlBody(body); + requestMessageBuilder.AddOrUpdateHeaderParameter("Content-Md5", + Utils.GetMD5SumStr(Encoding.UTF8.GetBytes(body))); + return requestMessageBuilder; + } +} diff --git a/LibExternal/Minio/DataModel/Args/SetObjectTagsArgs.cs b/LibExternal/Minio/DataModel/Args/SetObjectTagsArgs.cs new file mode 100644 index 0000000..e649f44 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/SetObjectTagsArgs.cs @@ -0,0 +1,55 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.DataModel.Tags; + +namespace Minio.DataModel.Args; + +public class SetObjectTagsArgs : ObjectVersionArgs +{ + public SetObjectTagsArgs() + { + RequestMethod = HttpMethod.Put; + } + + internal Tagging ObjectTags { get; private set; } + + public SetObjectTagsArgs WithTagging(Tagging tags) + { + if (tags is null) + throw new ArgumentNullException(nameof(tags)); + + ObjectTags = Tagging.GetObjectTags(tags.Tags); + return this; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + requestMessageBuilder.AddQueryParameter("tagging", ""); + if (!string.IsNullOrEmpty(VersionId)) requestMessageBuilder.AddQueryParameter("versionId", VersionId); + var body = ObjectTags.MarshalXML(); + requestMessageBuilder.AddXmlBody(body); + + return requestMessageBuilder; + } + + internal override void Validate() + { + base.Validate(); + if (ObjectTags is null || ObjectTags.Tags.Count == 0) + throw new InvalidOperationException("Unable to set empty tags."); + } +} diff --git a/LibExternal/Minio/DataModel/Args/SetPolicyArgs.cs b/LibExternal/Minio/DataModel/Args/SetPolicyArgs.cs new file mode 100644 index 0000000..905cc96 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/SetPolicyArgs.cs @@ -0,0 +1,45 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.Exceptions; + +namespace Minio.DataModel.Args; + +public class SetPolicyArgs : BucketArgs +{ + public SetPolicyArgs() + { + RequestMethod = HttpMethod.Put; + } + + internal string PolicyJsonString { get; private set; } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + if (string.IsNullOrEmpty(PolicyJsonString)) + throw new MinioException("SetPolicyArgs needs the policy to be set to the right JSON contents."); + + requestMessageBuilder.AddQueryParameter("policy", ""); + requestMessageBuilder.AddJsonBody(PolicyJsonString); + return requestMessageBuilder; + } + + public SetPolicyArgs WithPolicy(string policy) + { + PolicyJsonString = policy; + return this; + } +} diff --git a/LibExternal/Minio/DataModel/Args/SetVersioningArgs.cs b/LibExternal/Minio/DataModel/Args/SetVersioningArgs.cs new file mode 100644 index 0000000..0da8086 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/SetVersioningArgs.cs @@ -0,0 +1,71 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Text; +using Minio.Exceptions; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +public class SetVersioningArgs : BucketArgs +{ + internal VersioningStatus CurrentVersioningStatus; + + public SetVersioningArgs() + { + RequestMethod = HttpMethod.Put; + CurrentVersioningStatus = VersioningStatus.Off; + } + + internal override void Validate() + { + Utils.ValidateBucketName(BucketName); + if (CurrentVersioningStatus > VersioningStatus.Suspended) + throw new UnexpectedMinioException("CurrentVersioningStatus invalid value ."); + } + + public SetVersioningArgs WithVersioningEnabled() + { + CurrentVersioningStatus = VersioningStatus.Enabled; + return this; + } + + public SetVersioningArgs WithVersioningSuspended() + { + CurrentVersioningStatus = VersioningStatus.Suspended; + return this; + } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + var config = new VersioningConfiguration(CurrentVersioningStatus == VersioningStatus.Enabled); + + var body = Utils.MarshalXML(config, "http://s3.amazonaws.com/doc/2006-03-01/"); + requestMessageBuilder.AddXmlBody(body); + requestMessageBuilder.AddOrUpdateHeaderParameter("Content-Md5", + Utils.GetMD5SumStr(Encoding.UTF8.GetBytes(body))); + + requestMessageBuilder.AddQueryParameter("versioning", ""); + return requestMessageBuilder; + } + + internal enum VersioningStatus : ushort + { + Off = 0, + Enabled = 1, + Suspended = 2 + } +} diff --git a/LibExternal/Minio/DataModel/Args/StatObjectArgs.cs b/LibExternal/Minio/DataModel/Args/StatObjectArgs.cs new file mode 100644 index 0000000..03b17b0 --- /dev/null +++ b/LibExternal/Minio/DataModel/Args/StatObjectArgs.cs @@ -0,0 +1,98 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.DataModel.Encryption; + +namespace Minio.DataModel.Args; + +public class StatObjectArgs : ObjectConditionalQueryArgs +{ + public StatObjectArgs() + { + RequestMethod = HttpMethod.Head; + } + + internal long ObjectOffset { get; private set; } + internal long ObjectLength { get; private set; } + internal bool OffsetLengthSet { get; set; } + + internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) + { + if (!string.IsNullOrEmpty(VersionId)) + requestMessageBuilder.AddQueryParameter("versionId", $"{VersionId}"); + if (Headers.TryGetValue(S3ZipExtractKey, out var value)) + requestMessageBuilder.AddQueryParameter(S3ZipExtractKey, value); + + return requestMessageBuilder; + } + + internal override void Validate() + { + base.Validate(); + if (!string.IsNullOrEmpty(NotMatchETag) && !string.IsNullOrEmpty(MatchETag)) + throw new InvalidOperationException("Invalid to set both Etag match conditions " + nameof(NotMatchETag) + + " and " + nameof(MatchETag)); + + if (!ModifiedSince.Equals(default) && + !UnModifiedSince.Equals(default)) + throw new InvalidOperationException("Invalid to set both modified date match conditions " + + nameof(ModifiedSince) + " and " + nameof(UnModifiedSince)); + + if (OffsetLengthSet) + { + if (ObjectOffset < 0 || ObjectLength < 0) + throw new InvalidDataException(nameof(ObjectOffset) + " and " + nameof(ObjectLength) + + "cannot be less than 0."); + + if (ObjectOffset == 0 && ObjectLength == 0) + throw new InvalidDataException("Either " + nameof(ObjectOffset) + " or " + nameof(ObjectLength) + + " must be greater than 0."); + } + + Populate(); + } + + private void Populate() + { + Headers ??= new Dictionary(StringComparer.Ordinal); + if (SSE?.GetEncryptionType().Equals(EncryptionType.SSE_C) == true) SSE.Marshal(Headers); + if (OffsetLengthSet) + { + // "Range" header accepts byte start index and end index + if (ObjectLength > 0 && ObjectOffset > 0) + Headers["Range"] = "bytes=" + ObjectOffset + "-" + (ObjectOffset + ObjectLength - 1); + else if (ObjectLength == 0 && ObjectOffset > 0) + Headers["Range"] = "bytes=" + ObjectOffset + "-"; + else if (ObjectLength > 0 && ObjectOffset == 0) Headers["Range"] = "bytes=0-" + (ObjectLength - 1); + } + } + + public StatObjectArgs WithOffsetAndLength(long offset, long length) + { + OffsetLengthSet = true; + ObjectOffset = offset < 0 ? 0 : offset; + ObjectLength = length < 0 ? 0 : length; + return this; + } + + public StatObjectArgs WithLength(long length) + { + OffsetLengthSet = true; + ObjectOffset = 0; + ObjectLength = length < 0 ? 0 : length; + return this; + } +} diff --git a/LibExternal/Minio/DataModel/Bucket.cs b/LibExternal/Minio/DataModel/Bucket.cs new file mode 100644 index 0000000..6b2fcfa --- /dev/null +++ b/LibExternal/Minio/DataModel/Bucket.cs @@ -0,0 +1,28 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Globalization; + +namespace Minio.DataModel; + +[Serializable] +public class Bucket +{ + public string Name { get; set; } + public string CreationDate { get; set; } + + public DateTime CreationDateDateTime => DateTime.Parse(CreationDate, CultureInfo.InvariantCulture); +} diff --git a/LibExternal/Minio/DataModel/CopyConditions.cs b/LibExternal/Minio/DataModel/CopyConditions.cs new file mode 100644 index 0000000..1f5247a --- /dev/null +++ b/LibExternal/Minio/DataModel/CopyConditions.cs @@ -0,0 +1,138 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Collections.ObjectModel; + +namespace Minio.DataModel; + +/// +/// A container class to hold all the Conditions to be checked before copying an object. +/// +public class CopyConditions +{ + private readonly Dictionary copyConditions = new(StringComparer.Ordinal); + internal long byteRangeEnd = -1; + internal long byteRangeStart; + + /// + /// Get range size + /// + /// + public long ByteRange => byteRangeStart == -1 ? 0 : byteRangeEnd - byteRangeStart + 1; + + /// + /// Get all the set copy conditions map. + /// + /// + public ReadOnlyDictionary Conditions => new(copyConditions); + + /// + /// Clone CopyConditions object + /// + /// new CopyConditions object + public CopyConditions Clone() + { + var newcond = new CopyConditions(); + foreach (var item in copyConditions) newcond.copyConditions.Add(item.Key, item.Value); + newcond.byteRangeStart = byteRangeStart; + newcond.byteRangeEnd = byteRangeEnd; + return newcond; + } + + /// + /// Set modified condition, copy object modified since given time. + /// + /// + /// When date is null + public void SetModified(DateTime date) + { + copyConditions.Add("x-amz-copy-source-if-modified-since", date.ToUniversalTime().ToString("r")); + } + + /// + /// Unset modified condition, copy object modified since given time. + /// + /// + /// When date is null + public void SetUnmodified(DateTime date) + { + copyConditions.Add("x-amz-copy-source-if-unmodified-since", date.ToUniversalTime().ToString("r")); + } + + /// + /// Set matching ETag condition, copy object which matches + /// the following ETag. + /// + /// + /// When etag is null + public void SetMatchETag(string etag) + { + if (etag is null) throw new ArgumentException("ETag cannot be empty", nameof(etag)); + copyConditions.Add("x-amz-copy-source-if-match", etag); + } + + /// + /// Set matching ETag none condition, copy object which does not + /// match the following ETag. + /// + /// + /// When etag is null + public void SetMatchETagNone(string etag) + { + if (etag is null) throw new ArgumentException("ETag cannot be empty", nameof(etag)); + copyConditions.Add("x-amz-copy-source-if-none-match", etag); + } + + /// + /// Set replace metadata directive which specifies that server side copy needs to replace metadata + /// on destination with custom metadata provided in the request. + /// + public void SetReplaceMetadataDirective() + { + copyConditions.Add("x-amz-metadata-directive", "REPLACE"); + } + + /// + /// Return true if replace metadata directive is specified + /// + /// + public bool HasReplaceMetadataDirective() + { + foreach (var item in copyConditions) + if (item.Key.Equals("x-amz-metadata-directive", StringComparison.OrdinalIgnoreCase) && + item.Value.ToUpperInvariant().Equals("REPLACE", StringComparison.Ordinal)) + return true; + + return false; + } + + /// + /// Set Byte Range condition, copy object which falls within the + /// start and end byte range specified by user + /// + /// + /// + /// When firstByte is null or lastByte is null + public void SetByteRange(long firstByte, long lastByte) + { + if (firstByte < 0 || lastByte < firstByte) + throw new ArgumentOutOfRangeException(nameof(lastByte), + "Range start less than zero or range end less than range start"); + + byteRangeStart = firstByte; + byteRangeEnd = lastByte; + } +} diff --git a/LibExternal/Minio/DataModel/CreateBucketConfiguration.cs b/LibExternal/Minio/DataModel/CreateBucketConfiguration.cs new file mode 100644 index 0000000..ce4a714 --- /dev/null +++ b/LibExternal/Minio/DataModel/CreateBucketConfiguration.cs @@ -0,0 +1,55 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml; +using System.Xml.Serialization; +using Minio.DataModel.Notification; + +namespace Minio.DataModel; + +[XmlRoot(ElementName = "CreateBucketConfiguration", Namespace = "http://s3.amazonaws.com/doc/2006-03-01/")] +public class CreateBucketConfiguration +{ + public CreateBucketConfiguration() + { + LocationConstraint = null; + } + + public CreateBucketConfiguration(string location = null) + { + LocationConstraint = location; + } + + [XmlElement(ElementName = "LocationConstraint", IsNullable = true)] + public string LocationConstraint { get; set; } + + public string ToXml() + { + var settings = new XmlWriterSettings { OmitXmlDeclaration = true }; + using var ms = new MemoryStream(); + using var writer = XmlWriter.Create(ms, settings); + var names = new XmlSerializerNamespaces(); + names.Add(string.Empty, "http://s3.amazonaws.com/doc/2006-03-01/"); + + var cs = new XmlSerializer(typeof(BucketNotification)); + cs.Serialize(writer, this, names); + + ms.Flush(); + _ = ms.Seek(0, SeekOrigin.Begin); + using var sr = new StreamReader(ms); + return sr.ReadToEnd(); + } +} diff --git a/LibExternal/Minio/DataModel/DeleteObject.cs b/LibExternal/Minio/DataModel/DeleteObject.cs new file mode 100644 index 0000000..3ef82c5 --- /dev/null +++ b/LibExternal/Minio/DataModel/DeleteObject.cs @@ -0,0 +1,40 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel; + +[Serializable] +[XmlType(TypeName = "Object")] +public class DeleteObject +{ + public DeleteObject() + { + Key = null; + VersionId = null; + } + + public DeleteObject(string key, string versionId = null) + { + Key = key; + VersionId = versionId; + } + + [XmlElement("Key")] public string Key { get; set; } + + [XmlElement("VersionId")] public string VersionId { get; set; } +} diff --git a/LibExternal/Minio/DataModel/DeleteObjectsRequest.cs b/LibExternal/Minio/DataModel/DeleteObjectsRequest.cs new file mode 100644 index 0000000..3d63c44 --- /dev/null +++ b/LibExternal/Minio/DataModel/DeleteObjectsRequest.cs @@ -0,0 +1,41 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Collections.ObjectModel; +using System.Xml.Serialization; + +namespace Minio.DataModel; + +[Serializable] +[XmlType(TypeName = "Delete")] +public class DeleteObjectsRequest +{ + public DeleteObjectsRequest(Collection objectsList, bool quiet = true) + { + Quiet = quiet; + Objects = objectsList; + } + + public DeleteObjectsRequest() + { + Quiet = true; + Objects = new Collection(); + } + + [XmlElement("Quiet")] public bool Quiet { get; set; } + + [XmlElement("Object")] public Collection Objects { get; set; } +} diff --git a/LibExternal/Minio/DataModel/DeletedObject.cs b/LibExternal/Minio/DataModel/DeletedObject.cs new file mode 100644 index 0000000..0ae73f5 --- /dev/null +++ b/LibExternal/Minio/DataModel/DeletedObject.cs @@ -0,0 +1,31 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel; + +[Serializable] +public class DeletedObject +{ + [XmlElement("Key")] public string Key { get; set; } + + [XmlElement("VersionId")] public string VersionId { get; set; } + + [XmlElement("DeleteMarker")] public string DeleteMarker { get; set; } + + [XmlElement("DeleteMarkerVersionId")] public string DeleteMarkerVersionId { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Encryption/EncryptionType.cs b/LibExternal/Minio/DataModel/Encryption/EncryptionType.cs new file mode 100644 index 0000000..ef61cc1 --- /dev/null +++ b/LibExternal/Minio/DataModel/Encryption/EncryptionType.cs @@ -0,0 +1,27 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2019 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Encryption; + +// Type of Server-side encryption +public enum EncryptionType +{ +#pragma warning disable CA1707 // Identifiers should not contain underscores + SSE_C, + SSE_S3, + SSE_KMS +#pragma warning restore CA1707 // Identifiers should not contain underscores +} diff --git a/LibExternal/Minio/DataModel/Encryption/IServerSideEncryption.cs b/LibExternal/Minio/DataModel/Encryption/IServerSideEncryption.cs new file mode 100644 index 0000000..9045269 --- /dev/null +++ b/LibExternal/Minio/DataModel/Encryption/IServerSideEncryption.cs @@ -0,0 +1,29 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2019 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Encryption; + +/// +/// ServerSideEncryption interface +/// +public interface IServerSideEncryption +{ + // GetType() needs to return the type of Server-side encryption + EncryptionType GetEncryptionType(); + + // Marshals the Server-side encryption headers into dictionary + void Marshal(IDictionary headers); +} diff --git a/LibExternal/Minio/DataModel/Encryption/SSEC.cs b/LibExternal/Minio/DataModel/Encryption/SSEC.cs new file mode 100644 index 0000000..0e294be --- /dev/null +++ b/LibExternal/Minio/DataModel/Encryption/SSEC.cs @@ -0,0 +1,50 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2019 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.Helper; + +namespace Minio.DataModel.Encryption; + +/// +/// Server-side encryption with customer provided keys (SSE-C) +/// +public class SSEC : IServerSideEncryption +{ + // secret AES-256 Key + internal byte[] Key; + + public SSEC(byte[] key) + { + if (key is null || key.Length != 32) + throw new ArgumentException("Secret key needs to be a 256 bit AES Key", nameof(key)); + Key = key; + } + + public EncryptionType GetEncryptionType() + { + return EncryptionType.SSE_C; + } + + public virtual void Marshal(IDictionary headers) + { + if (headers is null) throw new ArgumentNullException(nameof(headers)); + + var md5SumStr = Utils.GetMD5SumStr(Key); + headers.Add("X-Amz-Server-Side-Encryption-Customer-Algorithm", "AES256"); + headers.Add("X-Amz-Server-Side-Encryption-Customer-Key", Convert.ToBase64String(Key)); + headers.Add("X-Amz-Server-Side-Encryption-Customer-Key-Md5", md5SumStr); + } +} diff --git a/LibExternal/Minio/DataModel/Encryption/SSECopy.cs b/LibExternal/Minio/DataModel/Encryption/SSECopy.cs new file mode 100644 index 0000000..9317120 --- /dev/null +++ b/LibExternal/Minio/DataModel/Encryption/SSECopy.cs @@ -0,0 +1,44 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2019 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.Helper; + +namespace Minio.DataModel.Encryption; + +/// +/// Server-side encryption option for source side SSE-C copy operation +/// +public class SSECopy : SSEC +{ + public SSECopy(byte[] key) : base(key) + { + } + + public override void Marshal(IDictionary headers) + { + if (headers is null) throw new ArgumentNullException(nameof(headers)); + + var md5SumStr = Utils.GetMD5SumStr(Key); + headers.Add("X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm", "AES256"); + headers.Add("X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key", Convert.ToBase64String(Key)); + headers.Add("X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-Md5", md5SumStr); + } + + public SSEC CloneToSSEC() + { + return new SSEC(Key); + } +} diff --git a/LibExternal/Minio/DataModel/Encryption/SSEKMS.cs b/LibExternal/Minio/DataModel/Encryption/SSEKMS.cs new file mode 100644 index 0000000..b5f14e5 --- /dev/null +++ b/LibExternal/Minio/DataModel/Encryption/SSEKMS.cs @@ -0,0 +1,82 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2019 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Text; +using Minio.Helper; + +namespace Minio.DataModel.Encryption; + +/// +/// Server-side encryption with AWS KMS managed keys +/// +public class SSEKMS : IServerSideEncryption +{ + public SSEKMS(string key, IDictionary context = null) + { + if (string.IsNullOrEmpty(key)) + throw new ArgumentException("KMS Key cannot be empty", nameof(key)); + Key = key; + Context = context; + } + + protected IDictionary Context { get; set; } + + // Specifies the customer master key(CMK).Cannot be null + protected string Key { get; set; } + + public EncryptionType GetEncryptionType() + { + return EncryptionType.SSE_KMS; + } + + public void Marshal(IDictionary headers) + { + if (headers is null) throw new ArgumentNullException(nameof(headers)); + + headers.Add(Constants.SSEKMSKeyId, Key); + headers.Add(Constants.SSEGenericHeader, "aws:kms"); + if (Context is not null) headers.Add(Constants.SSEKMSContext, MarshalContext()); + } + + /// + /// Serialize context into JSON string. + /// + /// Serialized JSON context + private string MarshalContext() + { + var sb = new StringBuilder(); + + sb.Append('{'); + var i = 0; + var len = Context.Count; + foreach (var pair in Context) + { + sb.Append('"').Append(pair.Key).Append('"'); + sb.Append(':'); + sb.Append('"').Append(pair.Value).Append('"'); + i++; + if (i != len) sb.Append(':'); + } + + sb.Append('}'); + ReadOnlySpan contextBytes = Encoding.UTF8.GetBytes(sb.ToString()); +#if NETSTANDARD + return Convert.ToBase64String(contextBytes.ToArray()); +#else + return Convert.ToBase64String(contextBytes); +#endif + } +} diff --git a/LibExternal/Minio/DataModel/Encryption/SSES3.cs b/LibExternal/Minio/DataModel/Encryption/SSES3.cs new file mode 100644 index 0000000..144b1ef --- /dev/null +++ b/LibExternal/Minio/DataModel/Encryption/SSES3.cs @@ -0,0 +1,37 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2019 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.Helper; + +namespace Minio.DataModel.Encryption; + +/// +/// Server-side encryption with S3 managed encryption keys (SSE-S3) +/// +public class SSES3 : IServerSideEncryption +{ + public EncryptionType GetEncryptionType() + { + return EncryptionType.SSE_S3; + } + + public virtual void Marshal(IDictionary headers) + { + if (headers is null) throw new ArgumentNullException(nameof(headers)); + + headers.Add(Constants.SSEGenericHeader, "AES256"); + } +} diff --git a/LibExternal/Minio/DataModel/Encryption/ServerSideEncryptionConfiguration.cs b/LibExternal/Minio/DataModel/Encryption/ServerSideEncryptionConfiguration.cs new file mode 100644 index 0000000..b0fa2b7 --- /dev/null +++ b/LibExternal/Minio/DataModel/Encryption/ServerSideEncryptionConfiguration.cs @@ -0,0 +1,50 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Encryption; + +[Serializable] +[XmlRoot(ElementName = "ServerSideEncryptionConfiguration", Namespace = "http://s3.amazonaws.com/doc/2006-03-01/")] +public class ServerSideEncryptionConfiguration +{ + public ServerSideEncryptionConfiguration() + { + Rule = null; + } + + public ServerSideEncryptionConfiguration(ServerSideEncryptionConfigurationRule rule = null) + { + Rule = rule; + } + + [XmlElement("Rule")] public ServerSideEncryptionConfigurationRule Rule { get; set; } + +#pragma warning disable CA1024 // Use properties where appropriate + public static ServerSideEncryptionConfiguration GetSSEConfigurationWithS3Rule() +#pragma warning restore CA1024 // Use properties where appropriate + { + return new ServerSideEncryptionConfiguration( + new ServerSideEncryptionConfigurationRule(ServerSideEncryptionConfigurationRule.SSE_AES256)); + } + + public static ServerSideEncryptionConfiguration GetSSEConfigurationWithKMSRule(string masterKeyId = null) + { + return new ServerSideEncryptionConfiguration( + new ServerSideEncryptionConfigurationRule(ServerSideEncryptionConfigurationRule.SSE_AWSKMS, masterKeyId)); + } +} diff --git a/LibExternal/Minio/DataModel/Encryption/ServerSideEncryptionConfigurationApply.cs b/LibExternal/Minio/DataModel/Encryption/ServerSideEncryptionConfigurationApply.cs new file mode 100644 index 0000000..11f4761 --- /dev/null +++ b/LibExternal/Minio/DataModel/Encryption/ServerSideEncryptionConfigurationApply.cs @@ -0,0 +1,43 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Encryption; + +[Serializable] +public class ServerSideEncryptionConfigurationApply +{ + public ServerSideEncryptionConfigurationApply() + { + SSEAlgorithm = ServerSideEncryptionConfigurationRule.SSE_AES256; + KMSMasterKeyId = null; + } + + public ServerSideEncryptionConfigurationApply(string algorithm = ServerSideEncryptionConfigurationRule.SSE_AES256, + string keyId = null) + { + if (string.IsNullOrEmpty(algorithm)) + throw new ArgumentException($"'{nameof(algorithm)}' cannot be null or empty.", nameof(algorithm)); + + SSEAlgorithm = algorithm; + KMSMasterKeyId = keyId; + } + + [XmlElement("KMSMasterKeyID")] public string KMSMasterKeyId { get; set; } + + [XmlElement("SSEAlgorithm")] public string SSEAlgorithm { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Encryption/ServerSideEncryptionConfigurationRule.cs b/LibExternal/Minio/DataModel/Encryption/ServerSideEncryptionConfigurationRule.cs new file mode 100644 index 0000000..dc01a03 --- /dev/null +++ b/LibExternal/Minio/DataModel/Encryption/ServerSideEncryptionConfigurationRule.cs @@ -0,0 +1,40 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Encryption; + +[Serializable] +[XmlRoot(ElementName = "Rule", Namespace = "http://s3.amazonaws.com/doc/2006-03-01/")] +public class ServerSideEncryptionConfigurationRule +{ + internal const string SSE_AES256 = "AES256"; + internal const string SSE_AWSKMS = "aws:kms"; + + public ServerSideEncryptionConfigurationRule() + { + Apply = new ServerSideEncryptionConfigurationApply(); + } + + public ServerSideEncryptionConfigurationRule(string algorithm = SSE_AES256, string keyId = null) + { + Apply = new ServerSideEncryptionConfigurationApply(algorithm, keyId); + } + + [XmlElement("ApplyServerSideEncryptionByDefault")] + public ServerSideEncryptionConfigurationApply Apply { get; set; } +} diff --git a/LibExternal/Minio/DataModel/ILM/AbortIncompleteMultipartUpload.cs b/LibExternal/Minio/DataModel/ILM/AbortIncompleteMultipartUpload.cs new file mode 100644 index 0000000..659aae1 --- /dev/null +++ b/LibExternal/Minio/DataModel/ILM/AbortIncompleteMultipartUpload.cs @@ -0,0 +1,44 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +/* + * AbortIncompleteMultipartUpload is a helper class to denote abort incomplete multipart upload information specifically for LifecycleRule. + * Please refer: + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycleConfiguration.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLifecycleConfiguration.html + */ + +namespace Minio.DataModel.ILM; + +[Serializable] +[XmlRoot(ElementName = "AbortIncompleteMultipartUpload")] +public class AbortIncompleteMultipartUpload +{ + public AbortIncompleteMultipartUpload() + { + DaysAfterInitiation = null; + } + + public AbortIncompleteMultipartUpload(uint daysAfterInitiation) + { + DaysAfterInitiation = daysAfterInitiation; + } + + [XmlElement(ElementName = "DaysAfterInitiation", IsNullable = true)] + internal uint? DaysAfterInitiation { get; set; } +} diff --git a/LibExternal/Minio/DataModel/ILM/AndOperator.cs b/LibExternal/Minio/DataModel/ILM/AndOperator.cs new file mode 100644 index 0000000..a50ce35 --- /dev/null +++ b/LibExternal/Minio/DataModel/ILM/AndOperator.cs @@ -0,0 +1,61 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Collections.ObjectModel; +using System.Xml.Serialization; +using Minio.DataModel.Tags; + +/* + * AndOperator is used with Lifecycle RuleFilter to bind the rules together. + * Please refer: + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycleConfiguration.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLifecycleConfiguration.html + */ + +namespace Minio.DataModel.ILM; + +[Serializable] +[XmlRoot(ElementName = "And")] +public class AndOperator +{ + public AndOperator() + { + } + + public AndOperator(string prefix, IList tag) + { + Prefix = prefix; + if (tag?.Count > 0) + Tags = new Collection(tag); + } + + public AndOperator(string prefix, IDictionary tags) + { + Prefix = prefix; + if (tags is null || tags.Count == 0) + return; + + Tags = new Collection(); + + foreach (var item in tags) + Tags.Add(new Tag(item.Key, item.Value)); + } + + [XmlElement("Prefix")] public string Prefix { get; set; } + + [XmlElement(ElementName = "Tag", IsNullable = false)] + public Collection Tags { get; set; } +} diff --git a/LibExternal/Minio/DataModel/ILM/Duration.cs b/LibExternal/Minio/DataModel/ILM/Duration.cs new file mode 100644 index 0000000..f76a60e --- /dev/null +++ b/LibExternal/Minio/DataModel/ILM/Duration.cs @@ -0,0 +1,46 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.ILM; + +[Serializable] +public abstract class Duration +{ + protected Duration() + { + ExpiryDate = null; + Days = null; + } + + protected Duration(DateTime date) + { + ExpiryDate = date.ToUniversalTime().Date.ToString("o") + ?? date.AddDays(1).AddSeconds(-1).ToUniversalTime().Date.ToString("o"); + } + + protected Duration(double days) + { + Days = days; + } + + [XmlElement(ElementName = "Date", IsNullable = true)] + public string ExpiryDate { get; set; } + + [XmlElement(ElementName = "Days", IsNullable = true)] + public double? Days { get; set; } +} diff --git a/LibExternal/Minio/DataModel/ILM/Expiration.cs b/LibExternal/Minio/DataModel/ILM/Expiration.cs new file mode 100644 index 0000000..f6d756d --- /dev/null +++ b/LibExternal/Minio/DataModel/ILM/Expiration.cs @@ -0,0 +1,42 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.ILM; + +[Serializable] +[XmlRoot(ElementName = "Expiration")] +public class Expiration : Duration +{ + public Expiration() + { + ExpiredObjectDeleteMarker = default; + } + + public Expiration(DateTime date, bool deleteMarker = false) : base(date) + { + if (date == default) + { + ExpiredObjectDeleteMarker = deleteMarker; + return; + } + + ExpiredObjectDeleteMarker = default; + } + + [XmlIgnore] public bool? ExpiredObjectDeleteMarker { get; set; } +} diff --git a/LibExternal/Minio/DataModel/ILM/LifecycleConfiguration.cs b/LibExternal/Minio/DataModel/ILM/LifecycleConfiguration.cs new file mode 100644 index 0000000..ccbce68 --- /dev/null +++ b/LibExternal/Minio/DataModel/ILM/LifecycleConfiguration.cs @@ -0,0 +1,85 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Collections.ObjectModel; +using System.Globalization; +using System.Xml; +using System.Xml.Serialization; +using Minio.Helper; + +/* + * Object representation of request XML used in these calls - PutBucketLifecycleConfiguration, GetBucketLifecycleConfiguration. + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycleConfiguration.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLifecycleConfiguration.html + * + */ + +namespace Minio.DataModel.ILM; + +[Serializable] +[XmlRoot(ElementName = "LifecycleConfiguration")] +public class LifecycleConfiguration +{ + public LifecycleConfiguration() + { + } + + public LifecycleConfiguration(IList rules) + { + if (rules is null || rules.Count == 0) + throw new ArgumentNullException(nameof(rules), + "Rules object cannot be empty. A finite set of Lifecycle Rules are needed for LifecycleConfiguration."); + + Rules = new Collection(rules); + } + + [XmlElement("Rule")] public Collection Rules { get; set; } + + public string MarshalXML() + { + XmlWriter xw = null; + + var str = string.Empty; + + try + { + var settings = new XmlWriterSettings { OmitXmlDeclaration = true }; + var ns = new XmlSerializerNamespaces(); + ns.Add(string.Empty, string.Empty); + + using var sw = new StringWriter(CultureInfo.InvariantCulture); + + var xs = new XmlSerializer(typeof(LifecycleConfiguration), ""); + using (xw = XmlWriter.Create(sw, settings)) + { + xs.Serialize(xw, this, ns); + xw.Flush(); + str = Utils.RemoveNamespaceInXML(sw.ToString()); + } + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + throw; + } + finally + { + xw?.Close(); + } + + return str; + } +} diff --git a/LibExternal/Minio/DataModel/ILM/LifecycleRule.cs b/LibExternal/Minio/DataModel/ILM/LifecycleRule.cs new file mode 100644 index 0000000..f31ce80 --- /dev/null +++ b/LibExternal/Minio/DataModel/ILM/LifecycleRule.cs @@ -0,0 +1,96 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +/* + * LifecycleRule is used within LifecycleConfiguration as an encapsulation of rules. + * Please refer: + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycleConfiguration.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLifecycleConfiguration.html + */ + +namespace Minio.DataModel.ILM; + +[Serializable] +[XmlRoot(ElementName = "Rule")] +public class LifecycleRule +{ + public static readonly string LifecycleRuleStatusEnabled = "Enabled"; + public static readonly string LifecycleRuleStatusDisabled = "Disabled"; + + private RuleFilter filter; + + public LifecycleRule() + { + filter = new RuleFilter(); + } + + public LifecycleRule(AbortIncompleteMultipartUpload abortIncompleteMultipartUpload, string id, + Expiration expiration, Transition transition, RuleFilter filter, + NoncurrentVersionExpiration noncurrentVersionExpiration, + NoncurrentVersionTransition noncurrentVersionTransition, + string status) + { + if (string.IsNullOrEmpty(status)) + throw new ArgumentException($"'{nameof(status)}' cannot be null or empty.", nameof(status)); + + if (!status.Equals(LifecycleRuleStatusEnabled, StringComparison.Ordinal) && + !status.Equals(LifecycleRuleStatusDisabled, StringComparison.Ordinal)) + throw new ArgumentException("Wrong value assignment", nameof(status)); + AbortIncompleteMultipartUploadObject = abortIncompleteMultipartUpload; + ID = id; + Expiration = expiration; + TransitionObject = transition; + Filter = filter; + NoncurrentVersionExpirationObject = noncurrentVersionExpiration; + NoncurrentVersionTransitionObject = noncurrentVersionTransition; + Status = status; + } + + [XmlElement(ElementName = "AbortIncompleteMultipartUpload", IsNullable = true)] + public AbortIncompleteMultipartUpload AbortIncompleteMultipartUploadObject { get; set; } + + [XmlElement(ElementName = "ID")] public string ID { get; set; } + + [XmlElement(ElementName = "Expiration", IsNullable = true)] + public Expiration Expiration { get; set; } + + [XmlElement(ElementName = "Transition", IsNullable = true)] + public Transition TransitionObject { get; set; } + + [XmlElement("Filter", IsNullable = true)] + public RuleFilter Filter + { + get => filter; + set + { + // The filter must not be missing, even if it is empty. + if (value is null) + filter = new RuleFilter(); + else + filter = value; + } + } + + [XmlElement("NoncurrentVersionExpiration", IsNullable = true)] + public NoncurrentVersionExpiration NoncurrentVersionExpirationObject { get; set; } + + [XmlElement("NoncurrentVersionTransition", IsNullable = true)] + public NoncurrentVersionTransition NoncurrentVersionTransitionObject { get; set; } + + [XmlElement("Status")] public string Status { get; set; } +} diff --git a/LibExternal/Minio/DataModel/ILM/NoncurrentVersionExpiration.cs b/LibExternal/Minio/DataModel/ILM/NoncurrentVersionExpiration.cs new file mode 100644 index 0000000..1013dc2 --- /dev/null +++ b/LibExternal/Minio/DataModel/ILM/NoncurrentVersionExpiration.cs @@ -0,0 +1,49 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +/* + * NoncurrentVersionExpiration is used within LifecycleRule to specify when the noncurrent object expires. + * Please refer: + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycleConfiguration.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLifecycleConfiguration.html + */ + +namespace Minio.DataModel.ILM; + +[Serializable] +[XmlRoot(ElementName = "NoncurrentVersionExpiration")] +public class NoncurrentVersionExpiration +{ + public NoncurrentVersionExpiration() + { + NoncurrentDays = null; + NewerNoncurrentVersions = null; + } + + public NoncurrentVersionExpiration(uint nonCurrentDays, uint? newerNoncurrentVersions = null) + { + NoncurrentDays = nonCurrentDays; + NewerNoncurrentVersions = newerNoncurrentVersions; + } + + [XmlElement(ElementName = "NoncurrentDays", IsNullable = true)] + public uint? NoncurrentDays { get; set; } + + [XmlElement(ElementName = "NewerNoncurrentVersions", IsNullable = true)] + public uint? NewerNoncurrentVersions { get; set; } +} diff --git a/LibExternal/Minio/DataModel/ILM/NoncurrentVersionTransition.cs b/LibExternal/Minio/DataModel/ILM/NoncurrentVersionTransition.cs new file mode 100644 index 0000000..8befa7f --- /dev/null +++ b/LibExternal/Minio/DataModel/ILM/NoncurrentVersionTransition.cs @@ -0,0 +1,46 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +/* + * NoncurrentVersionTransition is used within LifecycleRule to specify when the noncurrent object transitions and which storage class is to be used. + * Please refer: + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycleConfiguration.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLifecycleConfiguration.html + */ + +namespace Minio.DataModel.ILM; + +[Serializable] +[XmlRoot(ElementName = "NoncurrentVersionTransition")] +public class NoncurrentVersionTransition : NoncurrentVersionExpiration +{ + public NoncurrentVersionTransition() + { + StorageClass = null; + } + + public NoncurrentVersionTransition(uint nonCurrentDays, string storageClass) : base(nonCurrentDays) + { + Transition.CheckStorageClass(storageClass); + StorageClass = storageClass; + NoncurrentDays = nonCurrentDays; + } + + [XmlElement(ElementName = "StorageClass", IsNullable = true)] + public string StorageClass { get; set; } +} diff --git a/LibExternal/Minio/DataModel/ILM/RuleFilter.cs b/LibExternal/Minio/DataModel/ILM/RuleFilter.cs new file mode 100644 index 0000000..9fd39ca --- /dev/null +++ b/LibExternal/Minio/DataModel/ILM/RuleFilter.cs @@ -0,0 +1,58 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; +using Minio.DataModel.Tags; + +/* + * RuleFilter class used within LifecycleRule which encapsulates filter information. + * Please refer: + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycleConfiguration.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLifecycleConfiguration.html + */ + +namespace Minio.DataModel.ILM; + +[Serializable] +[XmlRoot(ElementName = "Filter")] +public class RuleFilter +{ + public RuleFilter() + { + } + + public RuleFilter(AndOperator theAndOperator, string prefix, Tagging tag) + { + TheAndOperator = theAndOperator; + if (string.IsNullOrWhiteSpace(prefix) || string.IsNullOrEmpty(prefix)) + Prefix = null; + else + Prefix = prefix; + if (tag?.TaggingSet.Tag.Count == 0) + tag = null; + else + Tag = tag; + } + + [XmlElement(ElementName = "And", IsNullable = true)] + public AndOperator TheAndOperator { get; set; } + + [XmlElement(ElementName = "Prefix", IsNullable = true)] + public string Prefix { get; set; } + + [XmlElement(ElementName = "Tag", IsNullable = true)] + public Tagging Tag { get; set; } +} diff --git a/LibExternal/Minio/DataModel/ILM/Transition.cs b/LibExternal/Minio/DataModel/ILM/Transition.cs new file mode 100644 index 0000000..05785c8 --- /dev/null +++ b/LibExternal/Minio/DataModel/ILM/Transition.cs @@ -0,0 +1,51 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +/* + * Transition class used within LifecycleRule used to specify the transition rule for the lifecycle rule. + * Please refer: + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycleConfiguration.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLifecycleConfiguration.html + */ + +namespace Minio.DataModel.ILM; + +[Serializable] +[XmlRoot(ElementName = "Transition")] +public class Transition : Duration +{ + public Transition() + { + } + + public Transition(DateTime date, string storageClass) : base(date) + { + CheckStorageClass(storageClass); + StorageClass = storageClass.ToUpperInvariant(); + } + + [XmlElement(ElementName = "StorageClass", IsNullable = true)] + public string StorageClass { get; set; } + + internal static void CheckStorageClass(string storageClass) + { + if (string.IsNullOrWhiteSpace(storageClass)) + throw new ArgumentException($"'{nameof(storageClass)}' cannot be null or whitespace.", + nameof(storageClass)); + } +} diff --git a/LibExternal/Minio/DataModel/Item.cs b/LibExternal/Minio/DataModel/Item.cs new file mode 100644 index 0000000..e3e7620 --- /dev/null +++ b/LibExternal/Minio/DataModel/Item.cs @@ -0,0 +1,57 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Globalization; + +namespace Minio.DataModel; + +[Serializable] +public class Item +{ + private string etag; + + public string Key { get; set; } + public string LastModified { get; set; } + + public string ETag + { + get => etag; + set + { + if (value is not null) + etag = value.Replace("\"", string.Empty, StringComparison.OrdinalIgnoreCase); + else + etag = null; + } + } + + public ulong Size { get; set; } + + public bool IsDir { get; set; } + + public string VersionId { get; set; } + public bool IsLatest { get; set; } + + public DateTime? LastModifiedDateTime + { + get + { + DateTime? dt = null; + if (!string.IsNullOrEmpty(LastModified)) dt = DateTime.Parse(LastModified, CultureInfo.InvariantCulture); + return dt; + } + } +} diff --git a/LibExternal/Minio/DataModel/MultiPartInfo.cs b/LibExternal/Minio/DataModel/MultiPartInfo.cs new file mode 100644 index 0000000..7e9c9da --- /dev/null +++ b/LibExternal/Minio/DataModel/MultiPartInfo.cs @@ -0,0 +1,8 @@ +namespace Minio.DataModel; + +public class MultiPartInfo +{ + public double PartSize { get; set; } + public double PartCount { get; set; } + public double LastPartSize { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Notification/Arn.cs b/LibExternal/Minio/DataModel/Notification/Arn.cs new file mode 100644 index 0000000..a150386 --- /dev/null +++ b/LibExternal/Minio/DataModel/Notification/Arn.cs @@ -0,0 +1,82 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Notification; + +/// +/// Arn holds ARN information that will be sent to the web service, +/// ARN desciption can be found in http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html +/// +public class Arn +{ + [XmlText] private readonly string arnString; + + public Arn() + { + } + + /// + /// Pass valid Arn string on aws to constructor + /// + /// + public Arn(string arnString) + { + if (string.IsNullOrEmpty(arnString)) + throw new ArgumentException($"'{nameof(arnString)}' cannot be null or empty.", nameof(arnString)); + + var parts = arnString.Split(':'); + if (parts.Length == 6) + { + Partition = parts[1]; + Service = parts[2]; + Region = parts[3]; + AccountID = parts[4]; + Resource = parts[5]; + this.arnString = arnString; + } + } + + /// + /// Constructs new ARN based on the given partition, service, region, account id and resource + /// + /// + /// + /// + /// + /// + public Arn(string partition, string service, string region, string accountId, string resource) + { + Partition = partition; + Service = service; + Region = region; + AccountID = accountId; + Resource = resource; + arnString = $"arn:{Partition}:{Service}:{Region}:{AccountID}:{Resource}"; + } + + private string Partition { get; } + private string Service { get; } + private string Region { get; } + private string AccountID { get; } + private string Resource { get; } + + public override string ToString() + { + return arnString; + } +} diff --git a/LibExternal/Minio/DataModel/Notification/BucketMeta.cs b/LibExternal/Minio/DataModel/Notification/BucketMeta.cs new file mode 100644 index 0000000..946b246 --- /dev/null +++ b/LibExternal/Minio/DataModel/Notification/BucketMeta.cs @@ -0,0 +1,28 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Text.Json.Serialization; + +namespace Minio.DataModel.Notification; + +public class BucketMeta +{ + [JsonPropertyName("arn")] public string Arn { get; set; } + + [JsonPropertyName("name")] public string Name { get; set; } + + [JsonPropertyName("ownerIdentity")] public Identity OwnerIdentity { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Notification/BucketNotification.cs b/LibExternal/Minio/DataModel/Notification/BucketNotification.cs new file mode 100644 index 0000000..d3912d2 --- /dev/null +++ b/LibExternal/Minio/DataModel/Notification/BucketNotification.cs @@ -0,0 +1,155 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017-2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Diagnostics.CodeAnalysis; +using System.Xml; +using System.Xml.Serialization; + +namespace Minio.DataModel.Notification; + +/// +/// Helper class to parse NotificationConfiguration from AWS S3 response XML. +/// +[Serializable] +[XmlRoot(ElementName = "NotificationConfiguration", Namespace = "http://s3.amazonaws.com/doc/2006-03-01/")] +public class BucketNotification +{ + public BucketNotification() + { + LambdaConfigs = new List(); + TopicConfigs = new List(); + QueueConfigs = new List(); + } + + [XmlElement("CloudFunctionConfiguration")] + [SuppressMessage("Design", "CA1002:Do not expose generic lists", + Justification = "Needs to be concrete type for XML deserialization")] + public List LambdaConfigs { get; set; } + + [XmlElement("TopicConfiguration")] + [SuppressMessage("Design", "CA1002:Do not expose generic lists", + Justification = "Needs to be concrete type for XML deserialization")] + public List TopicConfigs { get; set; } + + [XmlElement("QueueConfiguration")] + [SuppressMessage("Design", "CA1002:Do not expose generic lists", + Justification = "Needs to be concrete type for XML deserialization")] + public List QueueConfigs { get; set; } + + public string Name { get; set; } + + /// + /// AddTopic adds a given topic config to the general bucket notification config + /// + /// + public void AddTopic(TopicConfig topicConfig) + { + var isTopicFound = TopicConfigs.Exists(t => t.Topic.Equals(topicConfig)); + if (!isTopicFound) TopicConfigs.Add(topicConfig); + } + + /// + /// AddQueue adds a given queue config to the general bucket notification config + /// + /// + public void AddQueue(QueueConfig queueConfig) + { + var isQueueFound = QueueConfigs.Exists(t => t.Equals(queueConfig)); + if (!isQueueFound) QueueConfigs.Add(queueConfig); + } + + /// + /// AddLambda adds a given lambda config to the general bucket notification config + /// + /// + public void AddLambda(LambdaConfig lambdaConfig) + { + var isLambdaFound = LambdaConfigs.Exists(t => t.Lambda.Equals(lambdaConfig)); + if (!isLambdaFound) LambdaConfigs.Add(lambdaConfig); + } + + /// + /// RemoveTopicByArn removes all topic configurations that match the exact specified ARN + /// + /// + public void RemoveTopicByArn(Arn topicArn) + { + var numRemoved = TopicConfigs.RemoveAll(t => t.Topic.Equals(topicArn)); + } + + /// + /// RemoveQueueByArn removes all queue configurations that match the exact specified ARN + /// + /// + public void RemoveQueueByArn(Arn queueArn) + { + var numRemoved = QueueConfigs.RemoveAll(t => t.Queue.Equals(queueArn)); + } + + /// + /// RemoveLambdaByArn removes all lambda configurations that match the exact specified ARN + /// + /// + public void RemoveLambdaByArn(Arn lambdaArn) + { + var numRemoved = LambdaConfigs.RemoveAll(t => t.Lambda.Equals(lambdaArn)); + } + + /// + /// Helper methods to guide XMLSerializer + /// + /// + public bool ShouldSerializeLambdaConfigs() + { + return LambdaConfigs.Count > 0; + } + + public bool ShouldSerializeTopicConfigs() + { + return TopicConfigs.Count > 0; + } + + public bool ShouldSerializeQueueConfigs() + { + return QueueConfigs.Count > 0; + } + + public bool ShouldSerializeName() + { + return Name is not null; + } + + /// + /// Serializes the notification configuration as an XML string + /// + /// + public string ToXML() + { + var settings = new XmlWriterSettings { OmitXmlDeclaration = true }; + using var ms = new MemoryStream(); + using var xmlWriter = XmlWriter.Create(ms, settings); + var names = new XmlSerializerNamespaces(); + names.Add(string.Empty, "http://s3.amazonaws.com/doc/2006-03-01/"); + + var cs = new XmlSerializer(typeof(BucketNotification)); + cs.Serialize(xmlWriter, this, names); + + ms.Flush(); + _ = ms.Seek(0, SeekOrigin.Begin); + using var streamReader = new StreamReader(ms); + return streamReader.ReadToEnd(); + } +} diff --git a/LibExternal/Minio/DataModel/Notification/EventMeta.cs b/LibExternal/Minio/DataModel/Notification/EventMeta.cs new file mode 100644 index 0000000..b04337a --- /dev/null +++ b/LibExternal/Minio/DataModel/Notification/EventMeta.cs @@ -0,0 +1,40 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using System.Text.Json.Serialization; + +namespace Minio.DataModel.Notification; + +[DataContract] +public class EventMeta +{ + [DataMember] + [JsonPropertyName("bucket")] + public BucketMeta Bucket { get; set; } + + [DataMember] + [JsonPropertyName("configurationId")] + public string ConfigurationId { get; set; } + + [DataMember(Name = "object")] + [JsonPropertyName("object")] + public ObjectMeta ObjectMeta { get; set; } // C# won't allow the keyword 'object' as a name + + [DataMember] + [JsonPropertyName("schemaVersion")] + public string SchemaVersion { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Notification/EventType.cs b/LibExternal/Minio/DataModel/Notification/EventType.cs new file mode 100644 index 0000000..171a4c6 --- /dev/null +++ b/LibExternal/Minio/DataModel/Notification/EventType.cs @@ -0,0 +1,62 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Globalization; +using System.Xml.Serialization; + +namespace Minio.DataModel.Notification; + +/// +/// EventType is a S3 notification event associated to the bucket notification configuration +/// +public sealed class EventType +{ + // Valid Event types as described in: + // http://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html#notification-how-to-event-types-and-destinations + + public static readonly EventType ObjectCreatedAll = new("s3:ObjectCreated:*"); + public static readonly EventType ObjectCreatedPut = new("s3:ObjectCreated:Put"); + public static readonly EventType ObjectCreatedPost = new("s3:ObjectCreated:Post"); + public static readonly EventType ObjectCreatedCopy = new("s3:ObjectCreated:Copy"); + + public static readonly EventType ObjectCreatedCompleteMultipartUpload = + new("s3:ObjectCreated:CompleteMultipartUpload"); + + public static readonly EventType ObjectAccessedGet = new("s3:ObjectAccessed:Get"); + public static readonly EventType ObjectAccessedHead = new("s3:ObjectAccessed:Head"); + public static readonly EventType ObjectAccessedAll = new("s3:ObjectAccessed:*"); + public static readonly EventType ObjectRemovedAll = new("s3:ObjectRemoved:*"); + public static readonly EventType ObjectRemovedDelete = new("s3:ObjectRemoved:Delete"); + public static readonly EventType ObjectRemovedDeleteMarkerCreated = new("s3:ObjectRemoved:DeleteMarkerCreated"); + public static readonly EventType ReducedRedundancyLostObject = new("s3:ReducedRedundancyLostObject"); + + private EventType() + { + Value = null; + } + + public EventType(string value) + { + Value = value; + } + + [XmlText] public string Value { get; set; } + + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "EventType= {0}", Value); + } +} diff --git a/LibExternal/Minio/DataModel/Notification/Filter.cs b/LibExternal/Minio/DataModel/Notification/Filter.cs new file mode 100644 index 0000000..683a2e1 --- /dev/null +++ b/LibExternal/Minio/DataModel/Notification/Filter.cs @@ -0,0 +1,43 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Notification; + +// Filter - a tag in the notification xml structure which carries +// suffix/prefix filters +[Serializable] +public class Filter +{ + public Filter() + { + S3Key = new S3Key(); + } + + public Filter(S3Key key) + { + S3Key = key; + } + + [XmlElement("S3Key")] public S3Key S3Key { get; set; } + + // Helper to XMLSerializer which decides whether to serialize S3Key + public bool ShouldSerializeS3Key() + { + return S3Key.FilterRules.Count != 0; + } +} diff --git a/LibExternal/Minio/DataModel/Notification/FilterRule.cs b/LibExternal/Minio/DataModel/Notification/FilterRule.cs new file mode 100644 index 0000000..a18d04c --- /dev/null +++ b/LibExternal/Minio/DataModel/Notification/FilterRule.cs @@ -0,0 +1,53 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Notification; + +/// +/// FilterRule - child of S3Key, a tag in the notification xml which +/// carries suffix/prefix filters +/// +[Serializable] +public class FilterRule +{ + public FilterRule() + { + Name = null; + Value = null; + } + + public FilterRule(string name, string value) + { + Name = name; + Value = value; + } + + [XmlElement] public string Name { get; set; } + + [XmlElement] public string Value { get; set; } + + public bool ShouldSerializeName() + { + return Name is not null; + } + + public bool ShouldSerializeValue() + { + return Value is not null; + } +} diff --git a/LibExternal/Minio/DataModel/Notification/Identity.cs b/LibExternal/Minio/DataModel/Notification/Identity.cs new file mode 100644 index 0000000..48294b8 --- /dev/null +++ b/LibExternal/Minio/DataModel/Notification/Identity.cs @@ -0,0 +1,24 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Text.Json.Serialization; + +namespace Minio.DataModel.Notification; + +public class Identity +{ + [JsonPropertyName("principalId")] public string PrincipalId { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Notification/LambdaConfig.cs b/LibExternal/Minio/DataModel/Notification/LambdaConfig.cs new file mode 100644 index 0000000..1082fb4 --- /dev/null +++ b/LibExternal/Minio/DataModel/Notification/LambdaConfig.cs @@ -0,0 +1,59 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Notification; + +/// +/// LambdaConfig carries one single cloudfunction notification configuration +/// +[Serializable] +public class LambdaConfig : NotificationConfiguration +{ + public LambdaConfig() + { + } + + public LambdaConfig(string arn) : base(arn) + { + Lambda = arn; + } + + public LambdaConfig(Arn arn) : base(arn) + { + if (arn is null) throw new ArgumentNullException(nameof(arn)); + + Lambda = arn.ToString(); + } + + [XmlElement("CloudFunction")] public string Lambda { get; set; } + + // Implement equality for this object + public override bool Equals(object obj) + { + var other = (LambdaConfig)obj; + // If parameter is null return false. + if (obj is null) + return false; + return other.Lambda.Equals(Lambda, StringComparison.Ordinal); + } + + public override int GetHashCode() + { + return StringComparer.Ordinal.GetHashCode(Lambda); + } +} diff --git a/LibExternal/Minio/DataModel/Notification/MinioNotification.cs b/LibExternal/Minio/DataModel/Notification/MinioNotification.cs new file mode 100644 index 0000000..3023741 --- /dev/null +++ b/LibExternal/Minio/DataModel/Notification/MinioNotification.cs @@ -0,0 +1,34 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; + +namespace Minio.DataModel.Notification; + +/// +/// Helper class to deserialize notifications generated +/// from MinioNotificaitonRaw by ListenBucketNotifications +/// +[Serializable] +public class MinioNotification +{ + public string Err { get; set; } + + [SuppressMessage("Design", "MA0016:Prefer returning collection abstraction instead of implementation", + Justification = "Needs to be concrete type for XML deserialization")] + public Collection Records { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Notification/MinioNotificationRaw.cs b/LibExternal/Minio/DataModel/Notification/MinioNotificationRaw.cs new file mode 100644 index 0000000..6efa5e9 --- /dev/null +++ b/LibExternal/Minio/DataModel/Notification/MinioNotificationRaw.cs @@ -0,0 +1,32 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Notification; + +/// +/// Stores raw json events generated by ListenBucketNotifications +/// The Minio client doesn't depend on a JSON library so we can let +/// the caller use a library of their choice +/// +public class MinioNotificationRaw +{ + public MinioNotificationRaw(string json) + { + Json = json; + } + + public string Json { get; } +} diff --git a/LibExternal/Minio/DataModel/Notification/NotificationConfiguration.cs b/LibExternal/Minio/DataModel/Notification/NotificationConfiguration.cs new file mode 100644 index 0000000..1f569a8 --- /dev/null +++ b/LibExternal/Minio/DataModel/Notification/NotificationConfiguration.cs @@ -0,0 +1,120 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Diagnostics.CodeAnalysis; +using System.Xml.Serialization; + +namespace Minio.DataModel.Notification; + +/// +/// NotificationConfig - represents one single notification configuration +/// such as topic, queue or lambda configuration +/// +public class NotificationConfiguration +{ + public NotificationConfiguration() + { + Arn = null; + Events = new List(); + } + + public NotificationConfiguration(string arn) + { + Arn = new Arn(arn); + } + + public NotificationConfiguration(Arn arn) + { + Arn = arn; + } + + [XmlElement] public string Id { get; set; } + + [XmlElement("Event")] + [SuppressMessage("Design", "CA1002:Do not expose generic lists", Justification = "Using Range functions in code")] + public List Events { get; set; } + + [XmlElement("Filter")] public Filter Filter { get; set; } + + private Arn Arn { get; } + + public void AddEvents(IList evnt) + { + Events ??= new List(); + + Events.AddRange(evnt); + } + + /// + /// AddFilterSuffix sets the suffix configuration to the current notification config + /// + /// + public void AddFilterSuffix(string suffix) + { + Filter ??= new Filter(); + + var newFilterRule = new FilterRule("suffix", suffix); + // Replace any suffix rule if existing and add to the list otherwise + for (var i = 0; i < Filter.S3Key.FilterRules.Count; i++) + if (Filter.S3Key.FilterRules[i].Equals("suffix")) + { + Filter.S3Key.FilterRules[i] = newFilterRule; + return; + } + + Filter.S3Key.FilterRules.Add(newFilterRule); + } + + /// + /// AddFilterPrefix sets the prefix configuration to the current notification config + /// + /// + public void AddFilterPrefix(string prefix) + { + Filter ??= new Filter(); + + var newFilterRule = new FilterRule("prefix", prefix); + // Replace any prefix rule if existing and add to the list otherwise + for (var i = 0; i < Filter.S3Key.FilterRules.Count; i++) + if (Filter.S3Key.FilterRules[i].Equals("prefix")) + { + Filter.S3Key.FilterRules[i] = newFilterRule; + return; + } + + Filter.S3Key.FilterRules.Add(newFilterRule); + } + + public bool ShouldSerializeFilter() + { + return Filter is not null; + } + + public bool ShouldSerializeId() + { + return Id is not null; + } + + public bool ShouldSerializeEvents() + { + return Events?.Count > 0; + } + + internal bool IsIdSet() + { + return Id is not null; + } +} diff --git a/LibExternal/Minio/DataModel/Notification/NotificationEvent.cs b/LibExternal/Minio/DataModel/Notification/NotificationEvent.cs new file mode 100644 index 0000000..72db90a --- /dev/null +++ b/LibExternal/Minio/DataModel/Notification/NotificationEvent.cs @@ -0,0 +1,49 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; + +namespace Minio.DataModel.Notification; + +public class NotificationEvent +{ + [JsonPropertyName("awsRegion")] public string AwsRegion { get; set; } + + [JsonPropertyName("eventName")] public string EventName { get; set; } + + [JsonPropertyName("eventSource")] public string EventSource { get; set; } + + [JsonPropertyName("eventTime")] public string EventTime { get; set; } + + [JsonPropertyName("eventVersion")] public string EventVersion { get; set; } + + [JsonPropertyName("requestParameters")] + [SuppressMessage("Design", "MA0016:Prefer returning collection abstraction instead of implementation", + Justification = "Needs to be concrete type for XML deserialization")] + public Dictionary RequestParameters { get; set; } + + [JsonPropertyName("responseElements")] + [SuppressMessage("Design", "MA0016:Prefer returning collection abstraction instead of implementation", + Justification = "Needs to be concrete type for XML deserialization")] + public Dictionary ResponseElements { get; set; } + + [JsonPropertyName("s3")] public EventMeta S3 { get; set; } + + [JsonPropertyName("source")] public SourceInfo Source { get; set; } + + [JsonPropertyName("userIdentity")] public Identity UserIdentity { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Notification/ObjectMeta.cs b/LibExternal/Minio/DataModel/Notification/ObjectMeta.cs new file mode 100644 index 0000000..4fd7173 --- /dev/null +++ b/LibExternal/Minio/DataModel/Notification/ObjectMeta.cs @@ -0,0 +1,40 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; + +namespace Minio.DataModel.Notification; + +public class ObjectMeta +{ + [JsonPropertyName("contentType")] public string ContentType { get; set; } + + [JsonPropertyName("etag")] public string Etag { get; set; } + + [JsonPropertyName("key")] public string Key { get; set; } + + [JsonPropertyName("sequencer")] public string Sequencer { get; set; } + + [JsonPropertyName("size")] public int Size { get; set; } + + [JsonPropertyName("userMetadata")] + [SuppressMessage("Design", "MA0016:Prefer returning collection abstraction instead of implementation", + Justification = "Needs to be concrete type for XML deserialization")] + public Dictionary UserMetadata { get; set; } + + [JsonPropertyName("versionId")] public string VersionId { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Notification/QueueConfig.cs b/LibExternal/Minio/DataModel/Notification/QueueConfig.cs new file mode 100644 index 0000000..a1e5638 --- /dev/null +++ b/LibExternal/Minio/DataModel/Notification/QueueConfig.cs @@ -0,0 +1,56 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Notification; + +/// +/// QueueConfig carries one single queue notification configuration +/// +[Serializable] +public class QueueConfig : NotificationConfiguration +{ + public QueueConfig() + { + } + + public QueueConfig(string arn) : base(arn) + { + Queue = arn; + } + + public QueueConfig(Arn arn) : base(arn) + { + if (arn is null) throw new ArgumentNullException(nameof(arn)); + + Queue = arn.ToString(); + } + + public string Queue { get; set; } + + // Implement equality for this object + public override bool Equals(object obj) + { + var other = (QueueConfig)obj; + // If parameter is null return false. + if (other is null) return false; + return other.Queue.Equals(Queue, StringComparison.Ordinal); + } + + public override int GetHashCode() + { + return StringComparer.Ordinal.GetHashCode(Queue); + } +} diff --git a/LibExternal/Minio/DataModel/Notification/S3Key.cs b/LibExternal/Minio/DataModel/Notification/S3Key.cs new file mode 100644 index 0000000..c0a4687 --- /dev/null +++ b/LibExternal/Minio/DataModel/Notification/S3Key.cs @@ -0,0 +1,52 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Collections.ObjectModel; +using System.Xml.Serialization; + +namespace Minio.DataModel.Notification; + +/// +/// S3Key - child of Filter, a tag in the notification xml which carries suffix/prefix +/// filters and allows filtering event notifications based on S3 Object key's name +/// +[Serializable] +public class S3Key +{ + private Collection filterRules; + + [XmlElement("FilterRule")] + public Collection FilterRules + { + get + { + filterRules ??= new Collection(); + + return filterRules; + } + set => filterRules = value; + } + + internal bool IsFilterRulesSet() + { + return filterRules?.Count > 0; + } + + public bool ShouldSerializeFilterRules() + { + return filterRules.Count > 0; + } +} diff --git a/LibExternal/Minio/DataModel/Notification/SourceInfo.cs b/LibExternal/Minio/DataModel/Notification/SourceInfo.cs new file mode 100644 index 0000000..55fad8c --- /dev/null +++ b/LibExternal/Minio/DataModel/Notification/SourceInfo.cs @@ -0,0 +1,28 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Text.Json.Serialization; + +namespace Minio.DataModel.Notification; + +public class SourceInfo +{ + [JsonPropertyName("host")] public string Host { get; set; } + + [JsonPropertyName("port")] public string Port { get; set; } + + [JsonPropertyName("userAgent")] public string UserAgent { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Notification/TopicConfig.cs b/LibExternal/Minio/DataModel/Notification/TopicConfig.cs new file mode 100644 index 0000000..cd60b03 --- /dev/null +++ b/LibExternal/Minio/DataModel/Notification/TopicConfig.cs @@ -0,0 +1,62 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Notification; + +/// +/// TopicConfig carries one single topic notification configuration +/// +[Serializable] +public class TopicConfig : NotificationConfiguration +{ + public TopicConfig() + { + } + + public TopicConfig(string arn) : base(arn) + { + Topic = arn; + } + + public TopicConfig(Arn arn) : base(arn) + { + if (arn is null) throw new ArgumentNullException(nameof(arn)); + + Topic = arn.ToString(); + } + + [XmlElement] public string Topic { get; set; } + + /// + /// Implement equality for this object + /// + /// + /// + public override bool Equals(object obj) + { + var other = (TopicConfig)obj; + // If parameter is null return false. + if (other is null) return false; + return other.Topic.Equals(Topic, StringComparison.OrdinalIgnoreCase); + } + + public override int GetHashCode() + { + return StringComparer.Ordinal.GetHashCode(Topic); + } +} diff --git a/LibExternal/Minio/DataModel/ObjectLock/DefaultRetention.cs b/LibExternal/Minio/DataModel/ObjectLock/DefaultRetention.cs new file mode 100644 index 0000000..32e9ca7 --- /dev/null +++ b/LibExternal/Minio/DataModel/ObjectLock/DefaultRetention.cs @@ -0,0 +1,38 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.ObjectLock; + +[Serializable] +[XmlRoot(ElementName = "DefaultRetention", Namespace = "http://s3.amazonaws.com/doc/2006-03-01/")] +public class DefaultRetention +{ + public DefaultRetention() + { + } + + public DefaultRetention(int days, ObjectRetentionMode mode) + { + Days = days; + Mode = mode; + } + + [XmlElement("Days")] public int Days { get; set; } + + [XmlElement("Mode")] public ObjectRetentionMode Mode { get; set; } +} diff --git a/LibExternal/Minio/DataModel/ObjectLock/ObjectLegalHoldConfiguration.cs b/LibExternal/Minio/DataModel/ObjectLock/ObjectLegalHoldConfiguration.cs new file mode 100644 index 0000000..c1c4e5f --- /dev/null +++ b/LibExternal/Minio/DataModel/ObjectLock/ObjectLegalHoldConfiguration.cs @@ -0,0 +1,44 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.ObjectLock; + +[Serializable] +[XmlRoot(ElementName = "LegalHold", Namespace = "http://s3.amazonaws.com/doc/2006-03-01/")] + +// Legal Hold Configuration for the object. Status - {ON, OFF}. +public class ObjectLegalHoldConfiguration +{ + public ObjectLegalHoldConfiguration() + { + Status = "OFF"; + } + + public ObjectLegalHoldConfiguration(bool enable = true) + { + if (enable) + { + Status = "ON"; + return; + } + + Status = "OFF"; + } + + public string Status { get; set; } +} diff --git a/LibExternal/Minio/DataModel/ObjectLock/ObjectLockConfiguration.cs b/LibExternal/Minio/DataModel/ObjectLock/ObjectLockConfiguration.cs new file mode 100644 index 0000000..746a641 --- /dev/null +++ b/LibExternal/Minio/DataModel/ObjectLock/ObjectLockConfiguration.cs @@ -0,0 +1,41 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.ObjectLock; + +[Serializable] +[XmlRoot(ElementName = "ObjectLockConfiguration", Namespace = "http://s3.amazonaws.com/doc/2006-03-01/")] +public class ObjectLockConfiguration +{ + public const string LockEnabled = "Enabled"; + + public ObjectLockConfiguration() + { + ObjectLockEnabled = LockEnabled; + } + + public ObjectLockConfiguration(ObjectRetentionMode mode, int numOfDays) + { + ObjectLockEnabled = LockEnabled; + Rule = new ObjectLockRule(mode, numOfDays); + } + + [XmlElement("ObjectLockEnabled")] public string ObjectLockEnabled { get; set; } + + [XmlElement("Rule")] public ObjectLockRule Rule { get; set; } +} diff --git a/LibExternal/Minio/DataModel/ObjectLock/ObjectLockRule.cs b/LibExternal/Minio/DataModel/ObjectLock/ObjectLockRule.cs new file mode 100644 index 0000000..88736a6 --- /dev/null +++ b/LibExternal/Minio/DataModel/ObjectLock/ObjectLockRule.cs @@ -0,0 +1,36 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.ObjectLock; + +[Serializable] +[XmlRoot(ElementName = "ObjectLockRule", Namespace = "http://s3.amazonaws.com/doc/2006-03-01/")] +public class ObjectLockRule +{ + public ObjectLockRule() + { + DefaultRetention = null; + } + + public ObjectLockRule(ObjectRetentionMode mode, int retentionDurationDays) + { + DefaultRetention = new DefaultRetention(retentionDurationDays, mode); + } + + [XmlElement("DefaultRetention")] public DefaultRetention DefaultRetention { get; set; } +} diff --git a/LibExternal/Minio/DataModel/ObjectLock/ObjectRetentionConfiguration.cs b/LibExternal/Minio/DataModel/ObjectLock/ObjectRetentionConfiguration.cs new file mode 100644 index 0000000..a116e59 --- /dev/null +++ b/LibExternal/Minio/DataModel/ObjectLock/ObjectRetentionConfiguration.cs @@ -0,0 +1,40 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; +using Minio.Helper; + +namespace Minio.DataModel.ObjectLock; + +[Serializable] +[XmlRoot(ElementName = "Retention", Namespace = "http://s3.amazonaws.com/doc/2006-03-01/")] +public class ObjectRetentionConfiguration +{ + public ObjectRetentionConfiguration() + { + RetainUntilDate = null; + } + + public ObjectRetentionConfiguration(DateTime date, ObjectRetentionMode mode = ObjectRetentionMode.GOVERNANCE) + { + RetainUntilDate = Utils.To8601String(date); + Mode = mode; + } + + [XmlElement("Mode")] public ObjectRetentionMode Mode { get; set; } + + [XmlElement("RetainUntilDate")] public string RetainUntilDate { get; set; } +} diff --git a/LibExternal/Minio/DataModel/ObjectLock/ObjectRetentionMode.cs b/LibExternal/Minio/DataModel/ObjectLock/ObjectRetentionMode.cs new file mode 100644 index 0000000..8504480 --- /dev/null +++ b/LibExternal/Minio/DataModel/ObjectLock/ObjectRetentionMode.cs @@ -0,0 +1,23 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.ObjectLock; + +public enum ObjectRetentionMode +{ + GOVERNANCE, + COMPLIANCE +} diff --git a/LibExternal/Minio/DataModel/ObjectStat.cs b/LibExternal/Minio/DataModel/ObjectStat.cs new file mode 100644 index 0000000..ecc942e --- /dev/null +++ b/LibExternal/Minio/DataModel/ObjectStat.cs @@ -0,0 +1,175 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017-2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Text.RegularExpressions; +using Minio.DataModel.ObjectLock; +using Minio.Helper; + +namespace Minio.DataModel; + +public class ObjectStat +{ + private ObjectStat() + { + MetaData = new Dictionary(StringComparer.OrdinalIgnoreCase); + ExtraHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + public string ObjectName { get; private set; } + public long Size { get; private set; } + public DateTime LastModified { get; private set; } + public string ETag { get; private set; } + public string ContentType { get; private set; } + + [SuppressMessage("Design", "MA0016:Prefer returning collection abstraction instead of implementation", + Justification = "Needs to be concrete type for XML deserialization")] + public Dictionary MetaData { get; } + + public string VersionId { get; private set; } + public bool DeleteMarker { get; private set; } + + [SuppressMessage("Design", "MA0016:Prefer returning collection abstraction instead of implementation", + Justification = "Needs to be concrete type for XML deserialization")] + public Dictionary ExtraHeaders { get; } + + public uint? TaggingCount { get; private set; } + public string ArchiveStatus { get; private set; } + public DateTime? Expires { get; private set; } + public string ReplicationStatus { get; } + public ObjectRetentionMode? ObjectLockMode { get; private set; } + public DateTime? ObjectLockRetainUntilDate { get; private set; } + public bool? LegalHoldEnabled { get; private set; } + + public static ObjectStat FromResponseHeaders(string objectName, IDictionary responseHeaders) + { + if (string.IsNullOrEmpty(objectName)) + throw new ArgumentNullException(nameof(objectName), "Name of an object cannot be empty"); + if (responseHeaders is null) throw new ArgumentNullException(nameof(responseHeaders)); + + var objInfo = new ObjectStat { ObjectName = objectName }; + foreach (var paramName in responseHeaders.Keys) + { + var paramValue = responseHeaders[paramName]; + switch (paramName.ToLowerInvariant()) + { + case "content-length": + objInfo.Size = long.Parse(paramValue, NumberStyles.Number, CultureInfo.InvariantCulture); + break; + case "last-modified": + objInfo.LastModified = DateTime.Parse(paramValue, CultureInfo.InvariantCulture); + break; + case "etag": + objInfo.ETag = paramValue.Replace("\"", string.Empty, StringComparison.OrdinalIgnoreCase); + break; + case "content-type": + objInfo.ContentType = paramValue; + objInfo.MetaData["Content-Type"] = objInfo.ContentType; + break; + case "x-amz-version-id": + objInfo.VersionId = paramValue; + break; + case "x-amz-delete-marker": + objInfo.DeleteMarker = paramValue.Equals("true", StringComparison.OrdinalIgnoreCase); + break; + case "x-amz-archive-status": + objInfo.ArchiveStatus = paramValue; + break; + case "x-amz-tagging-count": + if (int.TryParse(paramValue, NumberStyles.Integer, CultureInfo.InvariantCulture, + out var tagCount) && tagCount >= 0) + objInfo.TaggingCount = (uint)tagCount; + break; + case "x-amz-expiration": + // x-amz-expiration header includes the expiration date and the corresponding rule id. + var expirationResponse = paramValue.Trim(); + var expiryDatePattern = + @"(Sun|Mon|Tue|Wed|Thu|Fri|Sat), \d{2} (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d{2}:\d{2}:\d{2} [A-Z]+"; + var expiryMatch = Regex.Match(expirationResponse, expiryDatePattern, RegexOptions.None, + TimeSpan.FromHours(1)); + if (expiryMatch.Success) + objInfo.Expires = DateTime.Parse(expiryMatch.Value, CultureInfo.CurrentCulture); + + break; + case "x-amz-object-lock-mode": + if (!string.IsNullOrWhiteSpace(paramValue)) + objInfo.ObjectLockMode = paramValue.Equals("governance", StringComparison.OrdinalIgnoreCase) + ? ObjectRetentionMode.GOVERNANCE + : ObjectRetentionMode.COMPLIANCE; + + break; + case "x-amz-object-lock-retain-until-date": + var lockUntilDate = paramValue; + if (!string.IsNullOrWhiteSpace(lockUntilDate)) + objInfo.ObjectLockRetainUntilDate = DateTime.Parse(lockUntilDate, CultureInfo.CurrentCulture); + + break; + case "x-amz-object-lock-legal-hold": + var legalHoldON = paramValue.Trim(); + if (!string.IsNullOrWhiteSpace(legalHoldON)) + objInfo.LegalHoldEnabled = legalHoldON.Equals("on", StringComparison.OrdinalIgnoreCase); + break; + default: + if (OperationsUtil.IsSupportedHeader(paramName)) + objInfo.MetaData[paramName] = paramValue; + else if (paramName.StartsWith("x-amz-meta-", StringComparison.OrdinalIgnoreCase)) + objInfo.MetaData[paramName["x-amz-meta-".Length..]] = paramValue; + else + objInfo.ExtraHeaders[paramName] = paramValue; + break; + } + } + + return objInfo; + } + + public override string ToString() + { + var versionInfo = "VersionId(None)"; + var legalHold = "LegalHold(None)"; + var taggingCount = "Tagging-Count(0)"; + var expires = "Expiry(None)"; + var objectLockInfo = "ObjectLock(None)"; + var archiveStatus = "Archive Status(None)"; + var replicationStatus = "Replication Status(None)"; + if (!string.IsNullOrWhiteSpace(VersionId)) + { + versionInfo = $"Version ID({VersionId})"; + if (DeleteMarker) versionInfo = $"Version ID({VersionId}, deleted)"; + } + + if (Expires is not null) expires = "Expiry(" + Utils.To8601String(Expires.Value) + ")"; + if (ObjectLockMode is not null) + { + objectLockInfo = "ObjectLock Mode(" + + (ObjectLockMode == ObjectRetentionMode.GOVERNANCE ? "GOVERNANCE" : "COMPLIANCE") + ")"; + objectLockInfo += " Retain Until Date(" + Utils.To8601String(ObjectLockRetainUntilDate.Value) + ")"; + } + + if (TaggingCount is not null) taggingCount = "Tagging-Count(" + TaggingCount.Value + ")"; + if (LegalHoldEnabled is not null) + legalHold = "LegalHold(" + (LegalHoldEnabled.Value ? "Enabled" : "Disabled") + ")"; + if (!string.IsNullOrWhiteSpace(ReplicationStatus)) + replicationStatus = "Replication Status(" + ReplicationStatus + ")"; + if (!string.IsNullOrWhiteSpace(ArchiveStatus)) archiveStatus = "Archive Status(" + ArchiveStatus + ")"; + var lineTwo = $"{expires} {objectLockInfo} {legalHold} {taggingCount} {archiveStatus} {replicationStatus}"; + + return + $"{ObjectName} : {versionInfo} Size({Size}) LastModified({LastModified}) ETag({ETag}) Content-Type({ContentType})" + + (string.IsNullOrWhiteSpace(lineTwo) ? "" : "\n" + lineTwo); + } +} diff --git a/LibExternal/Minio/DataModel/Part.cs b/LibExternal/Minio/DataModel/Part.cs new file mode 100644 index 0000000..a3fb448 --- /dev/null +++ b/LibExternal/Minio/DataModel/Part.cs @@ -0,0 +1,44 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel; + +[Serializable] +public class Part +{ + private string etag; + + public int PartNumber { get; set; } + public long Size { get; set; } + public DateTime LastModified { get; set; } + + public string ETag + { + get => etag; + set + { + if (value is not null) + etag = value.Replace("\"", string.Empty, StringComparison.OrdinalIgnoreCase); + else + etag = null; + } + } + + public long PartSize() + { + return Size; + } +} diff --git a/LibExternal/Minio/DataModel/PostPolicy.cs b/LibExternal/Minio/DataModel/PostPolicy.cs new file mode 100644 index 0000000..ceff523 --- /dev/null +++ b/LibExternal/Minio/DataModel/PostPolicy.cs @@ -0,0 +1,309 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Globalization; +using System.Text; + +namespace Minio.DataModel; + +public class PostPolicy +{ + public IList> Conditions { get; } = new List>(); + + /// + /// Get the populated dictionary of policy data. + /// + /// Dictionary of policy data + public IDictionary FormData { get; } = new Dictionary(StringComparer.Ordinal); + + public DateTime Expiration { get; set; } + public string Key { get; private set; } + public string Bucket { get; private set; } + + /// + /// Set expiration policy. + /// + /// Expiration time for the policy + public void SetExpires(DateTime expiration) + { + // this.formData.Add("expiration", expiration.ToString()); + Expiration = expiration; + } + + /// + /// Set key policy. + /// + /// Object name for the policy + public void SetKey(string key) + { + if (string.IsNullOrEmpty(key)) throw new ArgumentException("Object key cannot be null or empty", nameof(key)); + + Conditions.Add(new List<(string, string, string)> { ("eq", "$key", key) }); + // this.formData.Add("key", key); + Key = key; + } + + /// + /// Set key prefix policy. + /// + /// Object name prefix for the policy + public void SetKeyStartsWith(string keyStartsWith) + { + if (string.IsNullOrEmpty(keyStartsWith)) + throw new ArgumentException("Object key prefix cannot be null or empty", nameof(keyStartsWith)); + + Conditions.Add(new List<(string, string, string)> { ("starts-with", "$key", keyStartsWith) }); + // this.formData.Add("key", keyStartsWith); + } + + /// + /// Set bucket policy. + /// + /// Bucket name for the policy + public void SetBucket(string bucket) + { + if (string.IsNullOrEmpty(bucket)) + throw new ArgumentException("Bucket name cannot be null or empty", nameof(bucket)); + + Conditions.Add(new List<(string, string, string)> { ("eq", "$bucket", bucket) }); + // this.formData.Add("bucket", bucket); + Bucket = bucket; + } + + /// + /// Set cache control + /// + /// CacheControl for the policy + public void SetCacheControl(string cacheControl) + { + if (string.IsNullOrEmpty(cacheControl)) + throw new ArgumentException("Cache-Control argument cannot be null or empty", nameof(cacheControl)); + + Conditions.Add(new List<(string, string, string)> { ("eq", "$Cache-Control", cacheControl) }); + // this.formData.Add("Cache-Control", cacheControl); + } + + /// + /// Set content type policy. + /// + /// ContentType for the policy + public void SetContentType(string contentType) + { + if (string.IsNullOrEmpty(contentType)) + throw new ArgumentException("Content-Type argument cannot be null or empty", nameof(contentType)); + + Conditions.Add(new List<(string, string, string)> { ("eq", "$Content-Type", contentType) }); + // this.formData.Add("Content-Type", contentType); + } + + /// + /// Set content encoding + /// + /// ContentEncoding for the policy + public void SetContentEncoding(string contentEncoding) + { + if (string.IsNullOrEmpty(contentEncoding)) + throw new ArgumentException("Content-Encoding argument cannot be null or empty", + nameof(contentEncoding)); + + Conditions.Add(new List<(string, string, string)> { ("eq", "$Content-Encoding", contentEncoding) }); + // this.formData.Add("Content-Encoding", contentEncoding); + } + + /// + /// Set content length + /// + /// ContentLength for the policy + public void SetContentLength(long contentLength) + { + if (contentLength <= 0) throw new ArgumentException("Negative Content length", nameof(contentLength)); + + Conditions.Add(new List<(string, string, string)> + { + ("content-length-range", contentLength.ToString(CultureInfo.InvariantCulture), + contentLength.ToString(CultureInfo.InvariantCulture)) + }); + } + + /// + /// Set content range + /// + /// ContentRange for the policy + /// + public void SetContentRange(long startRange, long endRange) + { + if (startRange < 0 || endRange < 0) + throw new ArgumentOutOfRangeException(nameof(endRange), "Negative start or end range"); + + if (startRange > endRange) + throw new ArgumentException("Start range is greater than end range", nameof(startRange)); + + Conditions.Add(new List<(string, string, string)> + { + ("content-length-range", startRange.ToString(CultureInfo.InvariantCulture), + endRange.ToString(CultureInfo.InvariantCulture)) + }); + } + + /// + /// Set session token + /// + /// set session token + public void SetSessionToken(string sessionToken) + { + if (!string.IsNullOrEmpty(sessionToken)) + Conditions.Add( + new List<(string, string, string)> { ("eq", "$x-amz-security-token", sessionToken) }); + // this.formData.Add("x-amz-security-token", sessionToken); + } + + /// + /// Set the success action status of the object for this policy based upload. + /// + /// Success action status + public void SetSuccessStatusAction(string status) + { + if (string.IsNullOrEmpty(status)) throw new ArgumentException("Status is Empty", nameof(status)); + + Conditions.Add(new List<(string, string, string)> { ("eq", "$success_action_status", status) }); + // this.formData.Add("success_action_status", status); + } + + /// + /// Set user specified metadata as a key/value couple. + /// + /// Key and Value to insert in the metadata + /// + public void SetUserMetadata(string key, string value) + { + if (string.IsNullOrEmpty(key)) throw new ArgumentException("Key is Empty", nameof(key)); + + if (string.IsNullOrEmpty(value)) throw new ArgumentException("Value is Empty", nameof(value)); + + var headerName = $"x-amz-meta-{key}"; + Conditions.Add(new List<(string, string, string)> { ("eq", $"${headerName}", value) }); + FormData.Add(headerName, value); + } + + /// + /// Set signature algorithm policy. + /// + /// Set signature algorithm used for the policy + public void SetAlgorithm(string algorithm) + { + if (string.IsNullOrEmpty(algorithm)) + throw new ArgumentException("Algorithm argument cannot be null or empty", nameof(algorithm)); + + Conditions.Add(new List<(string, string, string)> { ("eq", "$x-amz-algorithm", algorithm) }); + // this.formData.Add("x-amz-algorithm", algorithm); + } + + /// + /// Set credential policy. + /// + /// Set credential string for the policy + public void SetCredential(string credential) + { + if (string.IsNullOrEmpty(credential)) + throw new ArgumentException("credential argument cannot be null or empty", nameof(credential)); + + Conditions.Add(new List<(string, string, string)> { ("eq", "$x-amz-credential", credential) }); + // this.formData.Add("x-amz-credential", credential); + } + + /// + /// Set date policy. + /// + /// Set date for the policy + public void SetDate(DateTime date) + { + var dateStr = date.ToUniversalTime().ToString("yyyyMMddTHHmmssZ", CultureInfo.InvariantCulture); + Conditions.Add(new List<(string, string, string)> { ("eq", "$x-amz-date", dateStr) }); + // this.formData.Add("x-amz-date", dateStr); + } + + /// + /// Serialize policy into JSON string. + /// + /// Serialized JSON policy + private ReadOnlySpan MarshalJSON() + { + var policyList = new List(); + foreach (var condition in Conditions) + policyList.Add("[\"" + condition[0].Item1 + "\",\"" + condition[0].Item2 + "\",\"" + + condition[0].Item3 + "\"]"); + + // expiration and policies will never be empty because of checks at PresignedPostPolicy() + var sb = new StringBuilder(); + _ = sb.Append('{'); + _ = sb.Append("\"expiration\":\"") + .Append(Expiration.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture)) + .Append('"') + .Append(','); + _ = sb.Append("\"conditions\":[").AppendJoin(",", policyList).Append(']'); + _ = sb.Append('}'); + return Encoding.UTF8.GetBytes(sb.ToString()); + } + + /// + /// Compute base64 encoded form of JSON policy. + /// + /// Base64 encoded string of JSON policy + public string Base64() + { + var policyStrBytes = MarshalJSON(); +#if NETSTANDARD + return Convert.ToBase64String(policyStrBytes.ToArray()); +#else + return Convert.ToBase64String(policyStrBytes); +#endif + } + + /// + /// Verify if bucket is set in policy. + /// + /// true if bucket is set + public bool IsBucketSet() + { + if (FormData.TryGetValue("bucket", out var value)) + if (!string.IsNullOrEmpty(value)) + return true; + + return false; + } + + /// + /// Verify if key is set in policy. + /// + /// true if key is set + public bool IsKeySet() + { + if (FormData.TryGetValue("key", out var value)) + if (!string.IsNullOrEmpty(value)) + return true; + + return false; + } + + /// + /// Verify if expiration is set in policy. + /// + /// true if expiration is set + public bool IsExpirationSet() + { + return !string.IsNullOrEmpty(Expiration.ToString(CultureInfo.InvariantCulture)); + } +} diff --git a/LibExternal/Minio/DataModel/Prefix.cs b/LibExternal/Minio/DataModel/Prefix.cs new file mode 100644 index 0000000..53e2637 --- /dev/null +++ b/LibExternal/Minio/DataModel/Prefix.cs @@ -0,0 +1,25 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel; + +[Serializable] +public class Prefix +{ + [XmlAttribute("Prefix")] public string Name { get; set; } +} diff --git a/LibExternal/Minio/DataModel/ProgressReport.cs b/LibExternal/Minio/DataModel/ProgressReport.cs new file mode 100644 index 0000000..1e7dd80 --- /dev/null +++ b/LibExternal/Minio/DataModel/ProgressReport.cs @@ -0,0 +1,23 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel; + +public class ProgressReport +{ + public int Percentage { get; set; } + public long TotalBytesTransferred { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Replication/AccessControlTranslation.cs b/LibExternal/Minio/DataModel/Replication/AccessControlTranslation.cs new file mode 100644 index 0000000..54e77c2 --- /dev/null +++ b/LibExternal/Minio/DataModel/Replication/AccessControlTranslation.cs @@ -0,0 +1,46 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +/* + * AccessControlTranslation class used within ReplicationDestination which is used to specify a cross-account if assigned. + * Please refer: + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketReplication.html + */ + +namespace Minio.DataModel.Replication; + +[Serializable] +[XmlRoot(ElementName = "AccessControlTranslation")] +public class AccessControlTranslation +{ + public AccessControlTranslation(string owner) + { + if (string.IsNullOrWhiteSpace(owner)) + throw new ArgumentException($"'{nameof(owner)}' cannot be null or whitespace.", nameof(owner)); + + Owner = owner; + } + + public AccessControlTranslation() + { + } + + [XmlElement("Owner")] public string Owner { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Replication/AndOperator.cs b/LibExternal/Minio/DataModel/Replication/AndOperator.cs new file mode 100644 index 0000000..d643c72 --- /dev/null +++ b/LibExternal/Minio/DataModel/Replication/AndOperator.cs @@ -0,0 +1,64 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Collections.ObjectModel; +using System.Xml.Serialization; +using Minio.DataModel.Tags; + +/* + * AndOperator class used within RuleFilter of ReplicationRule which is used to specify rule components and is equivalent of a Logical And for two or more predicates. + * Please refer: + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketReplication.html + */ + +namespace Minio.DataModel.Replication; + +[Serializable] +[XmlRoot(ElementName = "And")] +public class AndOperator +{ + public AndOperator() + { + } + + public AndOperator(string prefix, Tagging tagObj) + { + if (tagObj is null) throw new ArgumentNullException(nameof(tagObj)); + + Prefix = prefix; + Tags = new Collection(tagObj.TaggingSet.Tag); + } + + public AndOperator(string prefix, Collection tag) + { + Prefix = prefix; + Tags = tag; + } + + public AndOperator(string prefix, IDictionary tags) + { + if (tags is null) throw new ArgumentNullException(nameof(tags)); + + Prefix = prefix; + foreach (var item in tags) Tags.Add(new Tag(item.Key, item.Value)); + } + + [XmlElement("Prefix")] internal string Prefix { get; set; } + + [XmlElement("Tag")] public Collection Tags { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Replication/DeleteMarkerReplication.cs b/LibExternal/Minio/DataModel/Replication/DeleteMarkerReplication.cs new file mode 100644 index 0000000..c770d01 --- /dev/null +++ b/LibExternal/Minio/DataModel/Replication/DeleteMarkerReplication.cs @@ -0,0 +1,49 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +/* + * DeleteMarkerReplication class used within ReplicationRule to specify whether the service should replicate delete markers. + * Please refer: + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketReplication.html + */ + +namespace Minio.DataModel.Replication; + +[Serializable] +[XmlRoot(ElementName = "DeleteMarkerReplication")] +public class DeleteMarkerReplication +{ + public const string StatusEnabled = "Enabled"; + public const string StatusDisabled = "Disabled"; + + public DeleteMarkerReplication(string status) + { + if (string.IsNullOrEmpty(status)) + throw new ArgumentException($"'{nameof(status)}' cannot be null or empty.", nameof(status)); + + Status = status; + } + + public DeleteMarkerReplication() + { + } + + [XmlElement("Status")] public string Status { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Replication/DeleteReplication.cs b/LibExternal/Minio/DataModel/Replication/DeleteReplication.cs new file mode 100644 index 0000000..d2290b6 --- /dev/null +++ b/LibExternal/Minio/DataModel/Replication/DeleteReplication.cs @@ -0,0 +1,49 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +/* + * DeleteReplication class used within ReplicationRule to be used as a status within ReplicationRule. + * Please refer: + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketReplication.html + */ + +namespace Minio.DataModel.Replication; + +[Serializable] +[XmlRoot(ElementName = "DeleteReplication")] +public class DeleteReplication +{ + public const string StatusEnabled = "Enabled"; + public const string StatusDisabled = "Disabled"; + + public DeleteReplication(string status) + { + if (string.IsNullOrEmpty(status)) + throw new ArgumentException($"'{nameof(status)}' cannot be null or empty.", nameof(status)); + + Status = status; + } + + public DeleteReplication() + { + } + + [XmlElement("Status")] public string Status { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Replication/EncryptionConfiguration.cs b/LibExternal/Minio/DataModel/Replication/EncryptionConfiguration.cs new file mode 100644 index 0000000..00cc68d --- /dev/null +++ b/LibExternal/Minio/DataModel/Replication/EncryptionConfiguration.cs @@ -0,0 +1,47 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +/* + * EncryptionConfiguration class used within ReplicationDestination has the encryption-related information of the bucket. + * Please refer: + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketReplication.html + */ + +namespace Minio.DataModel.Replication; + +[Serializable] +public class EncryptionConfiguration +{ + public EncryptionConfiguration(string replicaKmsKeyID) + { + if (string.IsNullOrWhiteSpace(replicaKmsKeyID)) + throw new ArgumentException($"'{nameof(replicaKmsKeyID)}' cannot be null or whitespace.", + nameof(replicaKmsKeyID)); + + ReplicaKmsKeyID = replicaKmsKeyID; + } + + public EncryptionConfiguration() + { + } + + [XmlElement(ElementName = "ReplicaKmsKeyID", IsNullable = true)] + public string ReplicaKmsKeyID { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Replication/ExistingObjectReplication.cs b/LibExternal/Minio/DataModel/Replication/ExistingObjectReplication.cs new file mode 100644 index 0000000..685ff45 --- /dev/null +++ b/LibExternal/Minio/DataModel/Replication/ExistingObjectReplication.cs @@ -0,0 +1,50 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +/* + * ExistingObjectReplication class used within ReplicationRule is the configuration that specifies if existing source bucket objects can be replicated. + * Please refer: + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketReplication.html + */ + +namespace Minio.DataModel.Replication; + +[Serializable] +[XmlRoot(ElementName = "ExistingObjectReplication")] +public class ExistingObjectReplication +{ + public const string StatusEnabled = "Enabled"; + public const string StatusDisabled = "Disabled"; + + public ExistingObjectReplication() + { + Status = null; + } + + public ExistingObjectReplication(string status) + { + if (string.IsNullOrWhiteSpace(status)) + throw new ArgumentException($"'{nameof(status)}' cannot be null or whitespace.", nameof(status)); + + Status = status; + } + + [XmlElement("Status")] public string Status { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Replication/ReplicationConfiguration.cs b/LibExternal/Minio/DataModel/Replication/ReplicationConfiguration.cs new file mode 100644 index 0000000..8f3c515 --- /dev/null +++ b/LibExternal/Minio/DataModel/Replication/ReplicationConfiguration.cs @@ -0,0 +1,95 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Collections.ObjectModel; +using System.Globalization; +using System.Xml; +using System.Xml.Serialization; +using Minio.Helper; + +/* + * ReplicationConfiguration class used as a container for replication rules. Max number of rules is 100. Size of configuration allowed is 2MB. + * Please refer: + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketReplication.html + */ + +namespace Minio.DataModel.Replication; + +[Serializable] +[XmlRoot(ElementName = "ReplicationConfiguration")] +public class ReplicationConfiguration +{ + public ReplicationConfiguration() + { + } + + public ReplicationConfiguration(string role, Collection rules) + { + if (string.IsNullOrEmpty(role) || string.IsNullOrWhiteSpace(role)) + throw new ArgumentNullException(nameof(role), nameof(role) + " member cannot be empty."); + if (rules is null || rules.Count == 0) + throw new ArgumentNullException(nameof(rules), nameof(rules) + " member cannot be an empty list."); + if (rules.Count >= 1000) + throw new ArgumentOutOfRangeException(nameof(rules), + nameof(rules) + " Count of rules cannot exceed maximum limit of 1000."); + + Role = role; + Rules = rules; + } + + [XmlElement("Role")] public string Role { get; set; } + + [XmlElement("Rule")] public Collection Rules { get; set; } + + public string MarshalXML() + { + XmlWriter xw = null; + + var str = string.Empty; + + try + { + var settings = new XmlWriterSettings { OmitXmlDeclaration = true }; + var ns = new XmlSerializerNamespaces(); + ns.Add(string.Empty, string.Empty); + + using var sw = new StringWriter(CultureInfo.InvariantCulture); + + var xs = new XmlSerializer(typeof(ReplicationConfiguration), ""); + using (xw = XmlWriter.Create(sw, settings)) + { + xs.Serialize(xw, this, ns); + xw.Flush(); + + str = Utils.RemoveNamespaceInXML(sw.ToString()).Replace("\r", "", StringComparison.OrdinalIgnoreCase) + .Replace("\n", "", StringComparison.OrdinalIgnoreCase); + } + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + // throw ex; + } + finally + { + xw?.Close(); + } + + return str; + } +} diff --git a/LibExternal/Minio/DataModel/Replication/ReplicationDestination.cs b/LibExternal/Minio/DataModel/Replication/ReplicationDestination.cs new file mode 100644 index 0000000..98c1052 --- /dev/null +++ b/LibExternal/Minio/DataModel/Replication/ReplicationDestination.cs @@ -0,0 +1,69 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +/* + * ReplicationDestination class used within ReplicationRule to denote information about the destination of the operation. + * Please refer: + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketReplication.html + */ + +namespace Minio.DataModel.Replication; + +[Serializable] +[XmlRoot(ElementName = "Destination")] +public class ReplicationDestination +{ + public ReplicationDestination(AccessControlTranslation accessControlTranslation, string account, + string bucketARN, EncryptionConfiguration encryptionConfiguration, + ReplicationMetrics metrics, ReplicationTime replicationTime, string storageClass) + { + AccessControlTranslation = accessControlTranslation; + Account = account; + BucketARN = bucketARN; + EncryptionConfiguration = encryptionConfiguration; + Metrics = metrics; + ReplicationTime = replicationTime; + StorageClass = storageClass; + } + + public ReplicationDestination() + { + } + + [XmlElement(ElementName = "AccessControlTranslation", IsNullable = true)] + public AccessControlTranslation AccessControlTranslation { get; set; } + + [XmlElement(ElementName = "Account", IsNullable = true)] + public string Account { get; set; } + + [XmlElement("Bucket")] public string BucketARN { get; set; } + + [XmlElement(ElementName = "EncryptionConfiguration", IsNullable = true)] + public EncryptionConfiguration EncryptionConfiguration { get; set; } + + [XmlElement(ElementName = "Metrics", IsNullable = true)] + public ReplicationMetrics Metrics { get; set; } + + [XmlElement(ElementName = "ReplicationTime", IsNullable = true)] + public ReplicationTime ReplicationTime { get; set; } + + [XmlElement(ElementName = "StorageClass", IsNullable = true)] + public string StorageClass { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Replication/ReplicationMetrics.cs b/LibExternal/Minio/DataModel/Replication/ReplicationMetrics.cs new file mode 100644 index 0000000..99c3df1 --- /dev/null +++ b/LibExternal/Minio/DataModel/Replication/ReplicationMetrics.cs @@ -0,0 +1,50 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +/* + * Metrics class used within ReplicationDestination to denote metrics information. + * Please refer: + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketReplication.html + */ + +namespace Minio.DataModel.Replication; + +[Serializable] +[XmlRoot(ElementName = "Metrics")] +public class ReplicationMetrics + +{ + public ReplicationMetrics(string status, ReplicationTimeValue eventThreshold) + { + if (string.IsNullOrWhiteSpace(status)) + throw new ArgumentException($"'{nameof(status)}' member cannot be empty.", nameof(status)); + Status = status; + EventThreshold = eventThreshold; + } + + public ReplicationMetrics() + { + } + + [XmlElement(ElementName = "Status", IsNullable = true)] + public string Status { get; set; } + + [XmlElement("EventThreshold")] public ReplicationTimeValue EventThreshold { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Replication/ReplicationRule.cs b/LibExternal/Minio/DataModel/Replication/ReplicationRule.cs new file mode 100644 index 0000000..0b00f04 --- /dev/null +++ b/LibExternal/Minio/DataModel/Replication/ReplicationRule.cs @@ -0,0 +1,88 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +/* + * ReplicationRule class used within ReplicationConfiguration to encapsulate a rule used within replication configuration. + * Please refer: + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketReplication.html + */ + +namespace Minio.DataModel.Replication; + +[Serializable] +[XmlRoot(ElementName = "Rule")] +public class ReplicationRule +{ + public const string StatusEnabled = "Enabled"; + public const string StatusDisabled = "Disabled"; + + public ReplicationRule() + { + } + + public ReplicationRule(DeleteMarkerReplication deleteMarkerReplication, ReplicationDestination destination, + ExistingObjectReplication existingObjectReplication, RuleFilter filter, DeleteReplication deleteReplication, + uint priority, string id, string prefix, SourceSelectionCriteria sourceSelectionCriteria, string status) + { + if (string.IsNullOrWhiteSpace(status)) + throw new ArgumentNullException(nameof(status), nameof(status) + " cannot be null or empty."); + if (string.IsNullOrEmpty(id) || string.IsNullOrWhiteSpace(id)) + throw new ArgumentNullException(nameof(id), nameof(id) + " cannot be null or empty."); + DeleteMarkerReplication = deleteMarkerReplication; + Destination = destination ?? + throw new ArgumentNullException(nameof(destination), + nameof(destination) + " cannot be null or empty."); + ExistingObjectReplication = existingObjectReplication; + Filter = filter; + Priority = priority; + DeleteReplication = deleteReplication ?? + throw new ArgumentNullException(nameof(deleteReplication), + nameof(deleteReplication) + " cannot be null or empty."); + ID = id; + Prefix = prefix; + SourceSelectionCriteria = sourceSelectionCriteria; + Status = status; + } + + [XmlElement(ElementName = "DeleteMarkerReplication", IsNullable = true)] + public DeleteMarkerReplication DeleteMarkerReplication { get; set; } + + [XmlElement("Destination")] public ReplicationDestination Destination { get; set; } + + [XmlElement(ElementName = "ExistingObjectReplication", IsNullable = true)] + public ExistingObjectReplication ExistingObjectReplication { get; set; } + + [XmlElement(ElementName = "Filter", IsNullable = true)] + public RuleFilter Filter { get; set; } + + [XmlElement("Priority")] public uint Priority { get; set; } + + [XmlElement("ID")] public string ID { get; set; } + + [XmlElement(ElementName = "Prefix", IsNullable = true)] + public string Prefix { get; set; } + + [XmlElement("DeleteReplication")] public DeleteReplication DeleteReplication { get; set; } + + [XmlElement(ElementName = "SourceSelectionCriteria", IsNullable = true)] + public SourceSelectionCriteria SourceSelectionCriteria { get; set; } + + [XmlElement("Status")] public string Status { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Replication/ReplicationTime.cs b/LibExternal/Minio/DataModel/Replication/ReplicationTime.cs new file mode 100644 index 0000000..5540ba1 --- /dev/null +++ b/LibExternal/Minio/DataModel/Replication/ReplicationTime.cs @@ -0,0 +1,49 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +/* + * ReplicationTime class used within ReplicationDestination which has Replication Time Control information. + * Please refer: + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketReplication.html + */ + +namespace Minio.DataModel.Replication; + +[Serializable] +[XmlRoot(ElementName = "ReplicationTime")] +public class ReplicationTime +{ + public ReplicationTime() + { + } + + public ReplicationTime(ReplicationTimeValue time, string status) + { + if (string.IsNullOrEmpty(status)) + throw new ArgumentException($"'{nameof(status)}' cannot be null or empty.", nameof(status)); + + Time = time ?? throw new ArgumentNullException(nameof(time)); + Status = status; + } + + [XmlElement("Time")] public ReplicationTimeValue Time { get; set; } + + [XmlElement("Status")] public string Status { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Replication/ReplicationTimeValue.cs b/LibExternal/Minio/DataModel/Replication/ReplicationTimeValue.cs new file mode 100644 index 0000000..e8bc471 --- /dev/null +++ b/LibExternal/Minio/DataModel/Replication/ReplicationTimeValue.cs @@ -0,0 +1,43 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +/* + * ReplicationTimeValue class used within ReplicationTime which has Replication Time Control information. + * Please refer: + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketReplication.html + */ + +namespace Minio.DataModel.Replication; + +[Serializable] +[XmlRoot(ElementName = "ReplicationTimeValue")] +public class ReplicationTimeValue +{ + public ReplicationTimeValue(int minutes) + { + Minutes = minutes; + } + + public ReplicationTimeValue() + { + } + + [XmlElement("Minutes")] public int Minutes { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Replication/RuleFilter.cs b/LibExternal/Minio/DataModel/Replication/RuleFilter.cs new file mode 100644 index 0000000..0b30cc3 --- /dev/null +++ b/LibExternal/Minio/DataModel/Replication/RuleFilter.cs @@ -0,0 +1,53 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; +using Minio.DataModel.Tags; + +/* + * RuleFilter class used within ReplicationRule which encapsulates filter information. + * Please refer: + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketReplication.html + */ + +namespace Minio.DataModel.Replication; + +[Serializable] +[XmlRoot(ElementName = "Filter")] +public class RuleFilter +{ + public RuleFilter() + { + } + + public RuleFilter(AndOperator theAndOperator, string prefix, Tagging tag) + { + TheAndOperator = theAndOperator; + Prefix = prefix; + Tag = tag; + } + + [XmlElement(ElementName = "And", IsNullable = true)] + public AndOperator TheAndOperator { get; set; } + + [XmlElement(ElementName = "Prefix", IsNullable = true)] + public string Prefix { get; set; } + + [XmlElement(ElementName = "Tag", IsNullable = true)] + public Tagging Tag { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Replication/SourceSelectionCriteria.cs b/LibExternal/Minio/DataModel/Replication/SourceSelectionCriteria.cs new file mode 100644 index 0000000..a75ba31 --- /dev/null +++ b/LibExternal/Minio/DataModel/Replication/SourceSelectionCriteria.cs @@ -0,0 +1,44 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +/* + * SourceSelectionCriteria class used within ReplicationRule which describes additional filters for identifying the source objects that is being replicated. + * Please refer: + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketReplication.html + */ + +namespace Minio.DataModel.Replication; + +[Serializable] +[XmlRoot(ElementName = "SourceSelectionCriteria")] +public class SourceSelectionCriteria +{ + public SourceSelectionCriteria() + { + } + + public SourceSelectionCriteria(SseKmsEncryptedObjects sseKmsEncryptedObjects) + { + SSEKmsEncryptedObjects = sseKmsEncryptedObjects; + } + + [XmlElement(ElementName = "SseKmsEncryptedObjects", IsNullable = true)] + public SseKmsEncryptedObjects SSEKmsEncryptedObjects { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Replication/SseKmsEncryptedObjects.cs b/LibExternal/Minio/DataModel/Replication/SseKmsEncryptedObjects.cs new file mode 100644 index 0000000..9454490 --- /dev/null +++ b/LibExternal/Minio/DataModel/Replication/SseKmsEncryptedObjects.cs @@ -0,0 +1,48 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +/* + * SseKmsEncryptedObjects class used within SourceSelectionCriteria which has the filter information for the selection of objects encrypted with AWS KMS. + * Please refer: + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html + * https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketReplication.html + */ + +namespace Minio.DataModel.Replication; + +[Serializable] +[XmlRoot(ElementName = "SseKmsEncryptedObjects")] +public class SseKmsEncryptedObjects +{ + public const string StatusEnabled = "Enabled"; + public const string StatusDisabled = "Disabled"; + + public SseKmsEncryptedObjects() + { + } + + public SseKmsEncryptedObjects(string status) + { + if (string.IsNullOrWhiteSpace(status)) + throw new ArgumentException($"'{nameof(status)}' cannot be null or whitespace.", nameof(status)); + Status = status; + } + + [XmlElement("Status")] public string Status { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Response/CopyObjectResponse.cs b/LibExternal/Minio/DataModel/Response/CopyObjectResponse.cs new file mode 100644 index 0000000..8fbe62c --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/CopyObjectResponse.cs @@ -0,0 +1,38 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Net; +using System.Text; +using Minio.DataModel.Result; +using Minio.Helper; + +namespace Minio.DataModel.Response; + +internal class CopyObjectResponse : GenericResponse +{ + public CopyObjectResponse(HttpStatusCode statusCode, string responseContent, Type reqType) + : base(statusCode, responseContent) + { + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(responseContent).AsMemory().ToArray()); + if (reqType == typeof(CopyObjectResult)) + CopyObjectRequestResult = Utils.DeserializeXml(stream); + else + CopyPartRequestResult = Utils.DeserializeXml(stream); + } + + internal CopyObjectResult CopyObjectRequestResult { get; set; } + internal CopyPartResult CopyPartRequestResult { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Response/GenericResponse.cs b/LibExternal/Minio/DataModel/Response/GenericResponse.cs new file mode 100644 index 0000000..625945a --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/GenericResponse.cs @@ -0,0 +1,31 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Net; + +namespace Minio.DataModel.Response; + +public class GenericResponse +{ + internal GenericResponse(HttpStatusCode statusCode, string responseContent) + { + ResponseContent = responseContent; + ResponseStatusCode = statusCode; + } + + internal string ResponseContent { get; } + internal HttpStatusCode ResponseStatusCode { get; } +} diff --git a/LibExternal/Minio/DataModel/Response/GetBucketEncryptionResponse.cs b/LibExternal/Minio/DataModel/Response/GetBucketEncryptionResponse.cs new file mode 100644 index 0000000..bf4b0ce --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/GetBucketEncryptionResponse.cs @@ -0,0 +1,41 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Net; +using System.Text; +using Minio.DataModel.Encryption; +using Minio.Helper; + +namespace Minio.DataModel.Response; + +internal class GetBucketEncryptionResponse : GenericResponse +{ + internal GetBucketEncryptionResponse(HttpStatusCode statusCode, string responseContent) + : base(statusCode, responseContent) + { + if (string.IsNullOrEmpty(responseContent) || !HttpStatusCode.OK.Equals(statusCode)) + { + BucketEncryptionConfiguration = null; + return; + } + + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(responseContent).AsMemory().ToArray()); + BucketEncryptionConfiguration = + Utils.DeserializeXml(stream); + } + + internal ServerSideEncryptionConfiguration BucketEncryptionConfiguration { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Response/GetBucketLifecycleResponse.cs b/LibExternal/Minio/DataModel/Response/GetBucketLifecycleResponse.cs new file mode 100644 index 0000000..eac9b00 --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/GetBucketLifecycleResponse.cs @@ -0,0 +1,43 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Net; +using System.Text; +using Minio.DataModel.ILM; +using Minio.Helper; + +namespace Minio.DataModel.Response; + +internal class GetBucketLifecycleResponse : GenericResponse +{ + internal GetBucketLifecycleResponse(HttpStatusCode statusCode, string responseContent) + : base(statusCode, responseContent) + { + if (string.IsNullOrEmpty(responseContent) || + !HttpStatusCode.OK.Equals(statusCode)) + { + BucketLifecycle = null; + return; + } + + //Remove xmlns content for config serialization + responseContent = Utils.RemoveNamespaceInXML(responseContent); + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(responseContent).AsMemory().ToArray()); + BucketLifecycle = Utils.DeserializeXml(stream); + } + + internal LifecycleConfiguration BucketLifecycle { set; get; } +} diff --git a/LibExternal/Minio/DataModel/Response/GetBucketNotificationsResponse.cs b/LibExternal/Minio/DataModel/Response/GetBucketNotificationsResponse.cs new file mode 100644 index 0000000..0e07500 --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/GetBucketNotificationsResponse.cs @@ -0,0 +1,41 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Net; +using System.Text; +using Minio.DataModel.Notification; +using Minio.Helper; + +namespace Minio.DataModel.Response; + +internal class GetBucketNotificationsResponse : GenericResponse +{ + internal GetBucketNotificationsResponse(HttpStatusCode statusCode, string responseContent) + : base(statusCode, responseContent) + { + if (string.IsNullOrEmpty(responseContent) || + !HttpStatusCode.OK.Equals(statusCode)) + { + BucketNotificationConfiguration = new BucketNotification(); + return; + } + + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(responseContent).AsMemory().ToArray()); + BucketNotificationConfiguration = Utils.DeserializeXml(stream); + } + + internal BucketNotification BucketNotificationConfiguration { set; get; } +} diff --git a/LibExternal/Minio/DataModel/Response/GetBucketReplicationResponse.cs b/LibExternal/Minio/DataModel/Response/GetBucketReplicationResponse.cs new file mode 100644 index 0000000..c9f67e4 --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/GetBucketReplicationResponse.cs @@ -0,0 +1,41 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Net; +using System.Text; +using Minio.DataModel.Replication; +using Minio.Helper; + +namespace Minio.DataModel.Response; + +internal class GetBucketReplicationResponse : GenericResponse +{ + internal GetBucketReplicationResponse(HttpStatusCode statusCode, string responseContent) + : base(statusCode, responseContent) + { + if (string.IsNullOrEmpty(responseContent) || + !HttpStatusCode.OK.Equals(statusCode)) + { + Config = null; + return; + } + + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(responseContent).AsMemory().ToArray()); + Config = Utils.DeserializeXml(stream); + } + + internal ReplicationConfiguration Config { set; get; } +} diff --git a/LibExternal/Minio/DataModel/Response/GetBucketTagsResponse.cs b/LibExternal/Minio/DataModel/Response/GetBucketTagsResponse.cs new file mode 100644 index 0000000..87c9a34 --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/GetBucketTagsResponse.cs @@ -0,0 +1,43 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Net; +using System.Text; +using Minio.DataModel.Tags; +using Minio.Helper; + +namespace Minio.DataModel.Response; + +internal class GetBucketTagsResponse : GenericResponse +{ + internal GetBucketTagsResponse(HttpStatusCode statusCode, string responseContent) + : base(statusCode, responseContent) + { + if (string.IsNullOrEmpty(responseContent) || + !HttpStatusCode.OK.Equals(statusCode)) + { + BucketTags = null; + return; + } + + // Remove namespace from response content, if present. + responseContent = Utils.RemoveNamespaceInXML(responseContent); + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(responseContent).AsMemory().ToArray()); + BucketTags = Utils.DeserializeXml(stream); + } + + internal Tagging BucketTags { set; get; } +} diff --git a/LibExternal/Minio/DataModel/Response/GetLegalHoldResponse.cs b/LibExternal/Minio/DataModel/Response/GetLegalHoldResponse.cs new file mode 100644 index 0000000..37a898a --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/GetLegalHoldResponse.cs @@ -0,0 +1,48 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Net; +using System.Text; +using Minio.DataModel.ObjectLock; +using Minio.Helper; + +namespace Minio.DataModel.Response; + +public class GetLegalHoldResponse : GenericResponse +{ + public GetLegalHoldResponse(HttpStatusCode statusCode, string responseContent) + : base(statusCode, responseContent) + { + if (string.IsNullOrEmpty(responseContent) || !HttpStatusCode.OK.Equals(statusCode)) + { + CurrentLegalHoldConfiguration = null; + return; + } + + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(responseContent).AsMemory().ToArray()); + CurrentLegalHoldConfiguration = + Utils.DeserializeXml(stream); + + if (CurrentLegalHoldConfiguration is null + || string.IsNullOrEmpty(CurrentLegalHoldConfiguration.Status)) + Status = "OFF"; + else + Status = CurrentLegalHoldConfiguration.Status; + } + + internal ObjectLegalHoldConfiguration CurrentLegalHoldConfiguration { get; } + internal string Status { get; } +} diff --git a/LibExternal/Minio/DataModel/Response/GetMultipartUploadsListResponse.cs b/LibExternal/Minio/DataModel/Response/GetMultipartUploadsListResponse.cs new file mode 100644 index 0000000..177a589 --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/GetMultipartUploadsListResponse.cs @@ -0,0 +1,48 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Net; +using System.Text; +using System.Xml.Linq; +using Minio.DataModel.Result; +using Minio.Helper; + +namespace Minio.DataModel.Response; + +internal class GetMultipartUploadsListResponse : GenericResponse +{ + internal GetMultipartUploadsListResponse(HttpStatusCode statusCode, string responseContent) + : base(statusCode, responseContent) + { + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(responseContent).AsMemory().ToArray()); + var uploadsResult = Utils.DeserializeXml(stream); + var root = XDocument.Parse(responseContent); + XNamespace ns = Utils.DetermineNamespace(root); + + var itemCheck = root.Root.Descendants(ns + "Upload").FirstOrDefault(); + if (uploadsResult is null || itemCheck?.HasElements != true) return; + var uploads = from c in root.Root.Descendants(ns + "Upload") + select new Upload + { + Key = c.Element(ns + "Key").Value, + UploadId = c.Element(ns + "UploadId").Value, + Initiated = c.Element(ns + "Initiated").Value + }; + UploadResult = new Tuple>(uploadsResult, uploads.ToList()); + } + + internal Tuple> UploadResult { get; } +} diff --git a/LibExternal/Minio/DataModel/Response/GetObjectLockConfigurationResponse.cs b/LibExternal/Minio/DataModel/Response/GetObjectLockConfigurationResponse.cs new file mode 100644 index 0000000..0b50710 --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/GetObjectLockConfigurationResponse.cs @@ -0,0 +1,40 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Net; +using System.Text; +using Minio.DataModel.ObjectLock; +using Minio.Helper; + +namespace Minio.DataModel.Response; + +internal class GetObjectLockConfigurationResponse : GenericResponse +{ + internal GetObjectLockConfigurationResponse(HttpStatusCode statusCode, string responseContent) + : base(statusCode, responseContent) + { + if (string.IsNullOrEmpty(responseContent) || !HttpStatusCode.OK.Equals(statusCode)) + { + LockConfiguration = null; + return; + } + + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(responseContent).AsMemory().ToArray()); + LockConfiguration = Utils.DeserializeXml(stream); + } + + internal ObjectLockConfiguration LockConfiguration { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Response/GetObjectTagsResponse.cs b/LibExternal/Minio/DataModel/Response/GetObjectTagsResponse.cs new file mode 100644 index 0000000..287ebf4 --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/GetObjectTagsResponse.cs @@ -0,0 +1,42 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Net; +using System.Text; +using Minio.DataModel.Tags; +using Minio.Helper; + +namespace Minio.DataModel.Response; + +internal class GetObjectTagsResponse : GenericResponse +{ + public GetObjectTagsResponse(HttpStatusCode statusCode, string responseContent) + : base(statusCode, responseContent) + { + if (string.IsNullOrEmpty(responseContent) || + !HttpStatusCode.OK.Equals(statusCode)) + { + ObjectTags = null; + return; + } + + responseContent = Utils.RemoveNamespaceInXML(responseContent); + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(responseContent).AsMemory().ToArray()); + ObjectTags = Utils.DeserializeXml(stream); + } + + public Tagging ObjectTags { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Response/GetObjectsListResponse.cs b/LibExternal/Minio/DataModel/Response/GetObjectsListResponse.cs new file mode 100644 index 0000000..0dd50a4 --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/GetObjectsListResponse.cs @@ -0,0 +1,59 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Globalization; +using System.Net; +using System.Text; +using System.Xml.Linq; +using Minio.DataModel.Result; +using Minio.Helper; + +namespace Minio.DataModel.Response; + +internal class GetObjectsListResponse : GenericResponse +{ + internal ListBucketResult BucketResult; + internal Tuple> ObjectsTuple; + + internal GetObjectsListResponse(HttpStatusCode statusCode, string responseContent) + : base(statusCode, responseContent) + { + if (string.IsNullOrEmpty(responseContent) || + !HttpStatusCode.OK.Equals(statusCode)) + return; + + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(responseContent).AsMemory().ToArray()); + BucketResult = Utils.DeserializeXml(stream); + + var root = XDocument.Parse(responseContent); + XNamespace ns = Utils.DetermineNamespace(root); + + var items = from c in root.Root.Descendants(ns + "Contents") + select new Item + { + Key = c.Element(ns + "Key").Value, + LastModified = c.Element(ns + "LastModified").Value, + ETag = c.Element(ns + "ETag").Value, + Size = ulong.Parse(c.Element(ns + "Size").Value, + CultureInfo.CurrentCulture), + IsDir = false + }; + var prefixes = from c in root.Root.Descendants(ns + "CommonPrefixes") + select new Item { Key = c.Element(ns + "Prefix").Value, IsDir = true }; + items = items.Concat(prefixes); + ObjectsTuple = Tuple.Create(BucketResult, items.ToList()); + } +} diff --git a/LibExternal/Minio/DataModel/Response/GetObjectsVersionsListResponse.cs b/LibExternal/Minio/DataModel/Response/GetObjectsVersionsListResponse.cs new file mode 100644 index 0000000..106a3c9 --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/GetObjectsVersionsListResponse.cs @@ -0,0 +1,60 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Globalization; +using System.Net; +using System.Text; +using System.Xml.Linq; +using Minio.DataModel.Result; +using Minio.Helper; + +namespace Minio.DataModel.Response; + +internal class GetObjectsVersionsListResponse : GenericResponse +{ + internal ListVersionsResult BucketResult; + internal Tuple> ObjectsTuple; + + internal GetObjectsVersionsListResponse(HttpStatusCode statusCode, string responseContent) + : base(statusCode, responseContent) + { + if (string.IsNullOrEmpty(responseContent) || + !HttpStatusCode.OK.Equals(statusCode)) + return; + + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(responseContent).AsMemory().ToArray()); + BucketResult = Utils.DeserializeXml(stream); + + var root = XDocument.Parse(responseContent); + XNamespace ns = Utils.DetermineNamespace(root); + + var items = from c in root.Root.Descendants(ns + "Version") + select new Item + { + Key = c.Element(ns + "Key").Value, + LastModified = c.Element(ns + "LastModified").Value, + ETag = c.Element(ns + "ETag").Value, + VersionId = c.Element(ns + "VersionId").Value, + Size = ulong.Parse(c.Element(ns + "Size").Value, + CultureInfo.CurrentCulture), + IsDir = false + }; + var prefixes = from c in root.Root.Descendants(ns + "CommonPrefixes") + select new Item { Key = c.Element(ns + "Prefix").Value, IsDir = true }; + items = items.Concat(prefixes); + ObjectsTuple = Tuple.Create(BucketResult, items.ToList()); + } +} diff --git a/LibExternal/Minio/DataModel/Response/GetPolicyResponse.cs b/LibExternal/Minio/DataModel/Response/GetPolicyResponse.cs new file mode 100644 index 0000000..56bb9e7 --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/GetPolicyResponse.cs @@ -0,0 +1,43 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Net; +using System.Text; + +namespace Minio.DataModel.Response; + +internal class GetPolicyResponse : GenericResponse +{ + internal GetPolicyResponse(HttpStatusCode statusCode, string responseContent) + : base(statusCode, responseContent) + { + if (string.IsNullOrEmpty(responseContent) || + !HttpStatusCode.OK.Equals(statusCode)) + return; + + Initialize().Wait(); + } + + internal string PolicyJsonString { get; private set; } + + private async Task Initialize() + { + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(ResponseContent).AsMemory().ToArray()); + using var streamReader = new StreamReader(stream); + PolicyJsonString = await streamReader.ReadToEndAsync() + .ConfigureAwait(false); + } +} diff --git a/LibExternal/Minio/DataModel/Response/GetRetentionResponse.cs b/LibExternal/Minio/DataModel/Response/GetRetentionResponse.cs new file mode 100644 index 0000000..9459f60 --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/GetRetentionResponse.cs @@ -0,0 +1,41 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Net; +using System.Text; +using Minio.DataModel.ObjectLock; +using Minio.Helper; + +namespace Minio.DataModel.Response; + +internal class GetRetentionResponse : GenericResponse +{ + public GetRetentionResponse(HttpStatusCode statusCode, string responseContent) + : base(statusCode, responseContent) + { + if (string.IsNullOrEmpty(responseContent) && !HttpStatusCode.OK.Equals(statusCode)) + { + CurrentRetentionConfiguration = null; + return; + } + + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(responseContent).AsMemory().ToArray()); + CurrentRetentionConfiguration = + Utils.DeserializeXml(stream); + } + + internal ObjectRetentionConfiguration CurrentRetentionConfiguration { get; } +} diff --git a/LibExternal/Minio/DataModel/Response/GetVersioningResponse.cs b/LibExternal/Minio/DataModel/Response/GetVersioningResponse.cs new file mode 100644 index 0000000..ae5681a --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/GetVersioningResponse.cs @@ -0,0 +1,37 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Net; +using System.Text; +using Minio.Helper; + +namespace Minio.DataModel.Response; + +internal class GetVersioningResponse : GenericResponse +{ + internal GetVersioningResponse(HttpStatusCode statusCode, string responseContent) + : base(statusCode, responseContent) + { + if (string.IsNullOrEmpty(responseContent) || + !HttpStatusCode.OK.Equals(statusCode)) + return; + + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(responseContent).AsMemory().ToArray()); + VersioningConfig = Utils.DeserializeXml(stream); + } + + internal VersioningConfiguration VersioningConfig { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Response/ListBucketsResponse.cs b/LibExternal/Minio/DataModel/Response/ListBucketsResponse.cs new file mode 100644 index 0000000..16a1c6b --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/ListBucketsResponse.cs @@ -0,0 +1,37 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Net; +using System.Text; +using Minio.DataModel.Result; +using Minio.Helper; + +namespace Minio.DataModel.Response; + +internal class ListBucketsResponse : GenericResponse +{ + internal ListAllMyBucketsResult BucketsResult; + + internal ListBucketsResponse(HttpStatusCode statusCode, string responseContent) + : base(statusCode, responseContent) + { + if (string.IsNullOrEmpty(responseContent) || !HttpStatusCode.OK.Equals(statusCode)) + return; + + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(responseContent).AsMemory().ToArray()); + BucketsResult = Utils.DeserializeXml(stream); + } +} diff --git a/LibExternal/Minio/DataModel/Response/ListObjectVersionResponse.cs b/LibExternal/Minio/DataModel/Response/ListObjectVersionResponse.cs new file mode 100644 index 0000000..249939a --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/ListObjectVersionResponse.cs @@ -0,0 +1,75 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Web; +using Minio.DataModel.Args; +using Minio.DataModel.Result; + +namespace Minio.DataModel.Response; + +internal class ListObjectVersionResponse +{ + internal Item BucketObjectsLastItem; + internal IObserver ItemObservable; + + internal ListObjectVersionResponse(ListObjectsArgs args, Tuple> objectList, + IObserver obs) + { + ItemObservable = obs; + foreach (var item in objectList.Item2) + { + BucketObjectsLastItem = item; + if (string.Equals(objectList.Item1.EncodingType, "url", StringComparison.OrdinalIgnoreCase)) + item.Key = HttpUtility.UrlDecode(item.Key); + ItemObservable.OnNext(item); + } + + if (objectList.Item1.NextMarker is not null) + { + if (string.Equals(objectList.Item1.EncodingType, "url", StringComparison.OrdinalIgnoreCase)) + { + NextMarker = HttpUtility.UrlDecode(objectList.Item1.NextMarker); + NextKeyMarker = HttpUtility.UrlDecode(objectList.Item1.NextKeyMarker); + NextVerMarker = HttpUtility.UrlDecode(objectList.Item1.NextVersionIdMarker); + } + else + { + NextMarker = objectList.Item1.NextMarker; + NextKeyMarker = objectList.Item1.NextKeyMarker; + NextVerMarker = objectList.Item1.NextVersionIdMarker; + } + } + else if (BucketObjectsLastItem is not null) + { + if (string.Equals(objectList.Item1.EncodingType, "url", StringComparison.OrdinalIgnoreCase)) + { + NextMarker = HttpUtility.UrlDecode(BucketObjectsLastItem.Key); + NextKeyMarker = HttpUtility.UrlDecode(BucketObjectsLastItem.Key); + NextVerMarker = HttpUtility.UrlDecode(BucketObjectsLastItem.VersionId); + } + else + { + NextMarker = BucketObjectsLastItem.Key; + NextKeyMarker = BucketObjectsLastItem.Key; + NextVerMarker = BucketObjectsLastItem.VersionId; + } + } + } + + internal string NextMarker { get; } + internal string NextKeyMarker { get; } + internal string NextVerMarker { get; } +} diff --git a/LibExternal/Minio/DataModel/Response/ListObjectsItemResponse.cs b/LibExternal/Minio/DataModel/Response/ListObjectsItemResponse.cs new file mode 100644 index 0000000..33902ed --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/ListObjectsItemResponse.cs @@ -0,0 +1,58 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Web; +using Minio.DataModel.Args; +using Minio.DataModel.Result; + +namespace Minio.DataModel.Response; + +internal class ListObjectsItemResponse +{ + internal Item BucketObjectsLastItem; + internal IObserver ItemObservable; + + internal ListObjectsItemResponse(ListObjectsArgs args, Tuple> objectList, + IObserver obs) + { + ItemObservable = obs; + NextMarker = string.Empty; + foreach (var item in objectList.Item2) + { + BucketObjectsLastItem = item; + if (string.Equals(objectList.Item1.EncodingType, "url", StringComparison.OrdinalIgnoreCase)) + item.Key = HttpUtility.UrlDecode(item.Key); + ItemObservable.OnNext(item); + } + + if (objectList.Item1.NextMarker is not null) + { + if (string.Equals(objectList.Item1.EncodingType, "url", StringComparison.OrdinalIgnoreCase)) + NextMarker = HttpUtility.UrlDecode(objectList.Item1.NextMarker); + else + NextMarker = objectList.Item1.NextMarker; + } + else if (BucketObjectsLastItem is not null) + { + if (string.Equals(objectList.Item1.EncodingType, "url", StringComparison.OrdinalIgnoreCase)) + NextMarker = HttpUtility.UrlDecode(BucketObjectsLastItem.Key); + else + NextMarker = BucketObjectsLastItem.Key; + } + } + + internal string NextMarker { get; } +} diff --git a/LibExternal/Minio/DataModel/Response/NewMultipartUploadResponse.cs b/LibExternal/Minio/DataModel/Response/NewMultipartUploadResponse.cs new file mode 100644 index 0000000..1e9d54c --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/NewMultipartUploadResponse.cs @@ -0,0 +1,36 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Net; +using System.Text; +using Minio.DataModel.Result; +using Minio.Helper; + +namespace Minio.DataModel.Response; + +internal class NewMultipartUploadResponse : GenericResponse +{ + internal NewMultipartUploadResponse(HttpStatusCode statusCode, string responseContent) + : base(statusCode, responseContent) + { + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(responseContent).AsMemory().ToArray()); + var newUpload = Utils.DeserializeXml(stream); + + UploadId = newUpload.UploadId; + } + + internal string UploadId { get; } +} diff --git a/LibExternal/Minio/DataModel/Response/PresignedPostPolicyResponse.cs b/LibExternal/Minio/DataModel/Response/PresignedPostPolicyResponse.cs new file mode 100644 index 0000000..d02c4b7 --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/PresignedPostPolicyResponse.cs @@ -0,0 +1,33 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.DataModel.Args; + +namespace Minio.DataModel.Response; + +public class PresignedPostPolicyResponse +{ + public PresignedPostPolicyResponse(PresignedPostPolicyArgs args, Uri URI) + { + if (args is null) throw new ArgumentNullException(nameof(args)); + + if (URI is null) throw new ArgumentNullException(nameof(URI)); + + URIPolicyTuple = Tuple.Create(URI.AbsolutePath, args.Policy.FormData); + } + + internal Tuple> URIPolicyTuple { get; } +} diff --git a/LibExternal/Minio/DataModel/Response/PutObjectResponse.cs b/LibExternal/Minio/DataModel/Response/PutObjectResponse.cs new file mode 100644 index 0000000..b41b8dc --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/PutObjectResponse.cs @@ -0,0 +1,43 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Net; + +namespace Minio.DataModel.Response; + +public class PutObjectResponse : GenericResponse +{ + public PutObjectResponse(HttpStatusCode statusCode, string responseContent, + IDictionary responseHeaders, long size, string name) + : base(statusCode, responseContent) + { + if (responseHeaders is null) throw new ArgumentNullException(nameof(responseHeaders)); + + foreach (var parameter in responseHeaders) + if (parameter.Key.Equals("ETag", StringComparison.OrdinalIgnoreCase)) + { + Etag = parameter.Value; + break; + } + + Size = size; + ObjectName = name; + } + + public string Etag { get; set; } + public string ObjectName { get; set; } + public long Size { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Response/RemoveObjectsResponse.cs b/LibExternal/Minio/DataModel/Response/RemoveObjectsResponse.cs new file mode 100644 index 0000000..ad95a46 --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/RemoveObjectsResponse.cs @@ -0,0 +1,34 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Net; +using System.Text; +using Minio.DataModel.Result; +using Minio.Helper; + +namespace Minio.DataModel.Response; + +internal class RemoveObjectsResponse : GenericResponse +{ + internal RemoveObjectsResponse(HttpStatusCode statusCode, string responseContent) + : base(statusCode, responseContent) + { + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(responseContent).AsMemory().ToArray()); + DeletedObjectsResult = Utils.DeserializeXml(stream); + } + + internal DeleteObjectsResult DeletedObjectsResult { get; } +} diff --git a/LibExternal/Minio/DataModel/Response/SelectObjectContentResponse.cs b/LibExternal/Minio/DataModel/Response/SelectObjectContentResponse.cs new file mode 100644 index 0000000..1a51c36 --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/SelectObjectContentResponse.cs @@ -0,0 +1,45 @@ +using System.Net; +using Minio.DataModel.Select; + +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Response; + +internal class SelectObjectContentResponse : GenericResponse, IDisposable +{ + private bool disposed; + + internal SelectObjectContentResponse(HttpStatusCode statusCode, string responseContent, + ReadOnlyMemory responseRawBytes) + : base(statusCode, responseContent) + { + using var stream = new MemoryStream(responseRawBytes.ToArray()); + ResponseStream = new SelectResponseStream(stream); + } + + internal SelectResponseStream ResponseStream { get; private set; } + + public virtual void Dispose() + { + if (disposed) return; + + ResponseStream?.Dispose(); + ResponseStream = null; + + disposed = true; + } +} diff --git a/LibExternal/Minio/DataModel/Response/StatObjectResponse.cs b/LibExternal/Minio/DataModel/Response/StatObjectResponse.cs new file mode 100644 index 0000000..e1b847b --- /dev/null +++ b/LibExternal/Minio/DataModel/Response/StatObjectResponse.cs @@ -0,0 +1,33 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Net; +using Minio.DataModel.Args; + +namespace Minio.DataModel.Response; + +internal class StatObjectResponse : GenericResponse +{ + internal StatObjectResponse(HttpStatusCode statusCode, string responseContent, + IDictionary responseHeaders, StatObjectArgs args) + : base(statusCode, responseContent) + { + // StatObjectResponse object is populated with available stats from the response. + ObjectInfo = ObjectStat.FromResponseHeaders(args.ObjectName, responseHeaders); + } + + internal ObjectStat ObjectInfo { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Result/CopyObjectResult.cs b/LibExternal/Minio/DataModel/Result/CopyObjectResult.cs new file mode 100644 index 0000000..0b0bad3 --- /dev/null +++ b/LibExternal/Minio/DataModel/Result/CopyObjectResult.cs @@ -0,0 +1,27 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Result; + +[Serializable] +[XmlRoot(ElementName = "CopyObjectResult", Namespace = "http://s3.amazonaws.com/doc/2006-03-01/")] +public class CopyObjectResult +{ + public string ETag { get; set; } + public string LastModified { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Result/CopyPartResult.cs b/LibExternal/Minio/DataModel/Result/CopyPartResult.cs new file mode 100644 index 0000000..dd25846 --- /dev/null +++ b/LibExternal/Minio/DataModel/Result/CopyPartResult.cs @@ -0,0 +1,27 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Result; + +[Serializable] +[XmlRoot(ElementName = "CopyPartResult", Namespace = "http://s3.amazonaws.com/doc/2006-03-01/")] +public class CopyPartResult +{ + public string ETag { get; set; } + public string LastModified { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Result/DeleteObjectsResult.cs b/LibExternal/Minio/DataModel/Result/DeleteObjectsResult.cs new file mode 100644 index 0000000..7ae1d71 --- /dev/null +++ b/LibExternal/Minio/DataModel/Result/DeleteObjectsResult.cs @@ -0,0 +1,29 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Collections.ObjectModel; +using System.Xml.Serialization; +using Minio.Exceptions; + +namespace Minio.DataModel.Result; + +[Serializable] +[XmlRoot(ElementName = "DeleteResult", Namespace = "http://s3.amazonaws.com/doc/2006-03-01/")] +public class DeleteObjectsResult +{ + [XmlElement("Deleted")] public Collection ObjectsList { get; set; } + [XmlElement("Error")] public Collection ErrorList { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Result/InitiateMultipartUploadResult.cs b/LibExternal/Minio/DataModel/Result/InitiateMultipartUploadResult.cs new file mode 100644 index 0000000..2bff013 --- /dev/null +++ b/LibExternal/Minio/DataModel/Result/InitiateMultipartUploadResult.cs @@ -0,0 +1,26 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Result; + +[Serializable] +[XmlRoot(ElementName = "InitiateMultipartUploadResult", Namespace = "http://s3.amazonaws.com/doc/2006-03-01/")] +public class InitiateMultipartUploadResult +{ + public string UploadId { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Result/ListAllMyBucketsResult.cs b/LibExternal/Minio/DataModel/Result/ListAllMyBucketsResult.cs new file mode 100644 index 0000000..882bd69 --- /dev/null +++ b/LibExternal/Minio/DataModel/Result/ListAllMyBucketsResult.cs @@ -0,0 +1,32 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Collections.ObjectModel; +using System.Xml.Serialization; + +namespace Minio.DataModel.Result; + +[Serializable] +[XmlRoot(ElementName = "ListAllMyBucketsResult", Namespace = "http://s3.amazonaws.com/doc/2006-03-01/")] +[XmlInclude(typeof(Bucket))] +public class ListAllMyBucketsResult +{ + [XmlAttribute] public string Owner { get; set; } + + [XmlArray("Buckets")] + [XmlArrayItem(typeof(Bucket))] + public Collection Buckets { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Result/ListBucketResult.cs b/LibExternal/Minio/DataModel/Result/ListBucketResult.cs new file mode 100644 index 0000000..1df3b50 --- /dev/null +++ b/LibExternal/Minio/DataModel/Result/ListBucketResult.cs @@ -0,0 +1,36 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Result; + +[Serializable] +[XmlRoot(ElementName = "ListBucketResult", Namespace = "http://s3.amazonaws.com/doc/2006-03-01/")] +[XmlInclude(typeof(Item))] +[XmlInclude(typeof(Prefix))] +public class ListBucketResult +{ + public string Name { get; set; } + public string Prefix { get; set; } + public string NextMarker { get; set; } + public string MaxKeys { get; set; } + public string Delimiter { get; set; } + public string KeyCount { get; set; } + public bool IsTruncated { get; set; } + public string NextContinuationToken { get; set; } + public string EncodingType { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Result/ListMultipartUploadsResult.cs b/LibExternal/Minio/DataModel/Result/ListMultipartUploadsResult.cs new file mode 100644 index 0000000..1483d63 --- /dev/null +++ b/LibExternal/Minio/DataModel/Result/ListMultipartUploadsResult.cs @@ -0,0 +1,32 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Result; + +[Serializable] +[XmlRoot(ElementName = "ListMultipartUploadsResult", Namespace = "http://s3.amazonaws.com/doc/2006-03-01/")] +public class ListMultipartUploadsResult +{ + public string Bucket { get; set; } + public string KeyMarker { get; set; } + public string UploadIdMarker { get; set; } + public string NextKeyMarker { get; set; } + public string NextUploadIdMarker { get; set; } + public int MaxUploads { get; set; } + public bool IsTruncated { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Result/ListPartsResult.cs b/LibExternal/Minio/DataModel/Result/ListPartsResult.cs new file mode 100644 index 0000000..1bc5e0c --- /dev/null +++ b/LibExternal/Minio/DataModel/Result/ListPartsResult.cs @@ -0,0 +1,27 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Result; + +[Serializable] +[XmlRoot(ElementName = "ListPartsResult", Namespace = "http://s3.amazonaws.com/doc/2006-03-01/")] +public class ListPartsResult +{ + public int NextPartNumberMarker { get; set; } + public bool IsTruncated { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Result/ListVersionsResult.cs b/LibExternal/Minio/DataModel/Result/ListVersionsResult.cs new file mode 100644 index 0000000..a91d5b3 --- /dev/null +++ b/LibExternal/Minio/DataModel/Result/ListVersionsResult.cs @@ -0,0 +1,39 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Collections.ObjectModel; +using System.Xml.Serialization; + +namespace Minio.DataModel.Result; + +[Serializable] +[XmlRoot(ElementName = "ListVersionsResult", Namespace = "http://s3.amazonaws.com/doc/2006-03-01/")] +[XmlInclude(typeof(Prefix))] +public class ListVersionsResult +{ + public string Name { get; set; } + public string Prefix { get; set; } + public string NextMarker { get; set; } + public string MaxKeys { get; set; } + public string Delimiter { get; set; } + public bool IsTruncated { get; set; } + public string EncodingType { get; set; } + + [XmlElement("Version")] public Collection Versions { get; set; } + + public string NextKeyMarker { get; set; } + public string NextVersionIdMarker { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Result/ResponseResult.cs b/LibExternal/Minio/DataModel/Result/ResponseResult.cs new file mode 100644 index 0000000..0f0791d --- /dev/null +++ b/LibExternal/Minio/DataModel/Result/ResponseResult.cs @@ -0,0 +1,131 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2017-2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Net; +using System.Text; + +namespace Minio.DataModel.Result; + +public sealed class ResponseResult : IDisposable +{ + private readonly Dictionary headers = new(StringComparer.Ordinal); + private string content; + private ReadOnlyMemory contentBytes; + private bool disposed; + private Stream stream; + + public ResponseResult(HttpRequestMessage request, HttpResponseMessage response) + { + Request = request; + Response = response; + } + + public ResponseResult(HttpRequestMessage request, Exception exception) + : this(request, response: null) + { + Exception = exception; + } + + public Exception Exception { get; set; } + public HttpRequestMessage Request { get; } + public HttpResponseMessage Response { get; } + + public HttpStatusCode StatusCode + { + get + { +#pragma warning disable MA0099 // Use Explicit enum value instead of 0 + if (Response is null) return 0; +#pragma warning restore MA0099 // Use Explicit enum value instead of 0 + + return Response.StatusCode; + } + } + + public Stream ContentStream + { + get + { + if (Response is null) return null; + return stream ??= Response.Content.ReadAsStream(); + } + } + + public ReadOnlyMemory ContentBytes + { + get + { + if (ContentStream is null) + return ReadOnlyMemory.Empty; + + if (contentBytes.IsEmpty) + { + using var memoryStream = new MemoryStream(); + ContentStream.CopyTo(memoryStream); + contentBytes = new ReadOnlyMemory(memoryStream.GetBuffer(), 0, (int)memoryStream.Length); + } + + return contentBytes; + } + } + + public string Content + { + get + { + if (ContentBytes.Length == 0) return ""; + content ??= Encoding.UTF8.GetString(ContentBytes.Span); + return content; + } + } + + public IDictionary Headers + { + get + { + if (Response is null) return new Dictionary(StringComparer.Ordinal); + + if (headers.Count == 0) + { + if (Response.Content is not null) + foreach (var item in Response.Content.Headers) + headers.Add(item.Key, item.Value.FirstOrDefault()); + + foreach (var item in Response.Headers) headers.Add(item.Key, item.Value.FirstOrDefault()); + } + + return headers; + } + } + + public string ErrorMessage => Exception?.Message; + + public void Dispose() + { + if (disposed) return; + + stream?.Dispose(); + Request?.Dispose(); + Response?.Dispose(); + + content = null; + contentBytes = null; + stream = null; + + disposed = true; + } +} diff --git a/LibExternal/Minio/DataModel/Select/CSVFileHeaderInfo.cs b/LibExternal/Minio/DataModel/Select/CSVFileHeaderInfo.cs new file mode 100644 index 0000000..1ef6c89 --- /dev/null +++ b/LibExternal/Minio/DataModel/Select/CSVFileHeaderInfo.cs @@ -0,0 +1,39 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Select; + +[Serializable] +public sealed class CSVFileHeaderInfo +{ + // Constants for file header info. + public static readonly CSVFileHeaderInfo None = new("NONE"); + public static readonly CSVFileHeaderInfo Ignore = new("IGNORE"); + public static readonly CSVFileHeaderInfo Use = new("USE"); + + public CSVFileHeaderInfo() + { + } + + public CSVFileHeaderInfo(string value) + { + HeaderInfo = value; + } + + [XmlText] public string HeaderInfo { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Select/CSVInputOptions.cs b/LibExternal/Minio/DataModel/Select/CSVInputOptions.cs new file mode 100644 index 0000000..e3ad7f1 --- /dev/null +++ b/LibExternal/Minio/DataModel/Select/CSVInputOptions.cs @@ -0,0 +1,35 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Select; + +[Serializable] +public class CSVInputOptions +{ + public CSVFileHeaderInfo FileHeaderInfo { get; set; } + + [XmlElement(IsNullable = false)] public string RecordDelimiter { get; set; } + + [XmlElement(IsNullable = false)] public string FieldDelimiter { get; set; } + + [XmlElement(IsNullable = false)] public string QuoteCharacter { get; set; } + + [XmlElement(IsNullable = false)] public string QuoteEscapeCharacter { get; set; } + + [XmlElement(IsNullable = false)] public string Comments { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Select/CSVOutputOptions.cs b/LibExternal/Minio/DataModel/Select/CSVOutputOptions.cs new file mode 100644 index 0000000..7c4f71c --- /dev/null +++ b/LibExternal/Minio/DataModel/Select/CSVOutputOptions.cs @@ -0,0 +1,33 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Select; + +[Serializable] +public class CSVOutputOptions +{ + [XmlElement(IsNullable = false)] public CSVQuoteFields QuoteFields { get; set; } + + [XmlElement(IsNullable = false)] public string RecordDelimiter { get; set; } + + [XmlElement(IsNullable = false)] public string FieldDelimiter { get; set; } + + [XmlElement(IsNullable = false)] public string QuoteCharacter { get; set; } + + [XmlElement(IsNullable = false)] public string QuoteEscapeCharacter { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Select/CSVQuoteFields.cs b/LibExternal/Minio/DataModel/Select/CSVQuoteFields.cs new file mode 100644 index 0000000..2e2d7bb --- /dev/null +++ b/LibExternal/Minio/DataModel/Select/CSVQuoteFields.cs @@ -0,0 +1,38 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Select; + +[Serializable] +public sealed class CSVQuoteFields +{ + // Constants for csv quote styles. + public static readonly CSVQuoteFields Always = new("Always"); + public static readonly CSVQuoteFields AsNeeded = new("AsNeeded"); + + public CSVQuoteFields(string value) + { + QuoteFields = value; + } + + public CSVQuoteFields() + { + } + + [XmlText] public string QuoteFields { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Select/InputSerialization.cs b/LibExternal/Minio/DataModel/Select/InputSerialization.cs new file mode 100644 index 0000000..cacaa9f --- /dev/null +++ b/LibExternal/Minio/DataModel/Select/InputSerialization.cs @@ -0,0 +1,31 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Select; + +[Serializable] +public class InputSerialization +{ + public SelectCompressionType CompressionType { get; set; } + + [XmlElement("Parquet")] public ParquetInputOptions Parquet { get; set; } + + [XmlElement("CSV")] public CSVInputOptions CSV { get; set; } + + [XmlElement("JSON")] public JSONInputOptions JSON { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Select/JSONInputOptions.cs b/LibExternal/Minio/DataModel/Select/JSONInputOptions.cs new file mode 100644 index 0000000..7262f76 --- /dev/null +++ b/LibExternal/Minio/DataModel/Select/JSONInputOptions.cs @@ -0,0 +1,24 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Select; + +// JSONInputOptions json input specific options +[Serializable] +public class JSONInputOptions +{ + public JSONType Type { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Select/JSONOutputOptions.cs b/LibExternal/Minio/DataModel/Select/JSONOutputOptions.cs new file mode 100644 index 0000000..2f67c1a --- /dev/null +++ b/LibExternal/Minio/DataModel/Select/JSONOutputOptions.cs @@ -0,0 +1,24 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Select; + +// JSONOutputOptions - json output specific options +[Serializable] +public class JSONOutputOptions +{ + public string RecordDelimiter { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Select/JSONType.cs b/LibExternal/Minio/DataModel/Select/JSONType.cs new file mode 100644 index 0000000..155a370 --- /dev/null +++ b/LibExternal/Minio/DataModel/Select/JSONType.cs @@ -0,0 +1,38 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Select; + +[Serializable] +public sealed class JSONType +{ + // Constants for JSONTypes. + public static readonly JSONType Document = new("DOCUMENT"); + public static readonly JSONType Lines = new("LINES"); + + public JSONType() + { + } + + public JSONType(string value) + { + Type = value; + } + + [XmlText] public string Type { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Select/MessageType.cs b/LibExternal/Minio/DataModel/Select/MessageType.cs new file mode 100644 index 0000000..a9c40eb --- /dev/null +++ b/LibExternal/Minio/DataModel/Select/MessageType.cs @@ -0,0 +1,37 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Select; + +public sealed class MessageType +{ + // Constants for JSONTypes. + public static readonly MessageType Error = new("error"); + public static readonly MessageType Event = new("event"); + + public MessageType() + { + } + + public MessageType(string value) + { + Type = value; + } + + [XmlText] public string Type { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Select/OutputSerialization.cs b/LibExternal/Minio/DataModel/Select/OutputSerialization.cs new file mode 100644 index 0000000..5be430a --- /dev/null +++ b/LibExternal/Minio/DataModel/Select/OutputSerialization.cs @@ -0,0 +1,27 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Select; + +[Serializable] +public class OutputSerialization +{ + [XmlAttribute("CSV")] public CSVOutputOptions CSV { get; set; } + + [XmlAttribute("JSON")] public JSONOutputOptions JSON { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Select/ParquetInputOptions.cs b/LibExternal/Minio/DataModel/Select/ParquetInputOptions.cs new file mode 100644 index 0000000..bece675 --- /dev/null +++ b/LibExternal/Minio/DataModel/Select/ParquetInputOptions.cs @@ -0,0 +1,22 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Select; + +[Serializable] +public class ParquetInputOptions +{ +} diff --git a/LibExternal/Minio/DataModel/Select/ProgressMessage.cs b/LibExternal/Minio/DataModel/Select/ProgressMessage.cs new file mode 100644 index 0000000..db92457 --- /dev/null +++ b/LibExternal/Minio/DataModel/Select/ProgressMessage.cs @@ -0,0 +1,25 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Select; + +[Serializable] +[XmlRoot(ElementName = "Progress")] +public sealed class ProgressMessage : StatsMessage +{ +} diff --git a/LibExternal/Minio/DataModel/Select/QueryExpressionType.cs b/LibExternal/Minio/DataModel/Select/QueryExpressionType.cs new file mode 100644 index 0000000..b134ac7 --- /dev/null +++ b/LibExternal/Minio/DataModel/Select/QueryExpressionType.cs @@ -0,0 +1,37 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Select; + +[Serializable] +public sealed class QueryExpressionType +{ + // Constants for compression types under select API. + public static readonly QueryExpressionType SQL = new("SQL"); + + public QueryExpressionType() + { + } + + public QueryExpressionType(string value) + { + ExpressionType = value; + } + + [XmlText] public string ExpressionType { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Select/RequestProgress.cs b/LibExternal/Minio/DataModel/Select/RequestProgress.cs new file mode 100644 index 0000000..2f5761f --- /dev/null +++ b/LibExternal/Minio/DataModel/Select/RequestProgress.cs @@ -0,0 +1,34 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Select; + +[Serializable] +public class RequestProgress +{ + public RequestProgress() + { + } + + public RequestProgress(bool val) + { + Enable = val; + } + + [XmlElement(IsNullable = false)] public bool Enable { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Select/SelectCompressionType.cs b/LibExternal/Minio/DataModel/Select/SelectCompressionType.cs new file mode 100644 index 0000000..f040fea --- /dev/null +++ b/LibExternal/Minio/DataModel/Select/SelectCompressionType.cs @@ -0,0 +1,39 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Select; + +[Serializable] +public sealed class SelectCompressionType +{ + // Constants for compression types under select API. + public static readonly SelectCompressionType NONE = new("NONE"); + public static readonly SelectCompressionType GZIP = new("GZIP"); + public static readonly SelectCompressionType BZIP = new("BZIP2"); + + public SelectCompressionType() + { + } + + public SelectCompressionType(string value) + { + CompressionType = value; + } + + [XmlText] public string CompressionType { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Select/SelectEventType.cs b/LibExternal/Minio/DataModel/Select/SelectEventType.cs new file mode 100644 index 0000000..413f877 --- /dev/null +++ b/LibExternal/Minio/DataModel/Select/SelectEventType.cs @@ -0,0 +1,37 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Select; + +public sealed class SelectEventType +{ + // Constants for EventType. + public static readonly SelectEventType SelectEndEvent = new("End"); + public static readonly SelectEventType SelectRecordsEvent = new("Records"); + public static readonly SelectEventType SelectProgressEvent = new("Progress"); + public static readonly SelectEventType SelectStatsEvent = new("Stats"); + + private string Type; + + public SelectEventType() + { + } + + public SelectEventType(string value) + { + Type = value; + } +} diff --git a/LibExternal/Minio/DataModel/Select/SelectObjectInputSerialization.cs b/LibExternal/Minio/DataModel/Select/SelectObjectInputSerialization.cs new file mode 100644 index 0000000..e5a9062 --- /dev/null +++ b/LibExternal/Minio/DataModel/Select/SelectObjectInputSerialization.cs @@ -0,0 +1,31 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Select; + +[Serializable] +public class SelectObjectInputSerialization +{ + public SelectCompressionType CompressionType { get; set; } + + [XmlElement("Parquet")] public ParquetInputOptions Parquet { get; set; } + + [XmlElement("CSV")] public CSVInputOptions CSV { get; set; } + + [XmlElement("JSON")] public JSONInputOptions JSON { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Select/SelectObjectOptions.cs b/LibExternal/Minio/DataModel/Select/SelectObjectOptions.cs new file mode 100644 index 0000000..27c52ae --- /dev/null +++ b/LibExternal/Minio/DataModel/Select/SelectObjectOptions.cs @@ -0,0 +1,72 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Globalization; +using System.Xml; +using System.Xml.Serialization; +using Minio.DataModel.Encryption; + +namespace Minio.DataModel.Select; + +[Serializable] +[XmlRoot(ElementName = "SelectObjectContentRequest")] +public class SelectObjectOptions +{ + [XmlIgnore] public IServerSideEncryption SSE { get; set; } + + public string Expression { get; set; } + + [XmlElement("ExpressionType")] public QueryExpressionType ExpressionType { get; set; } + + public SelectObjectInputSerialization InputSerialization { get; set; } + public SelectObjectOutputSerialization OutputSerialization { get; set; } + public RequestProgress RequestProgress { get; set; } + + public string MarshalXML() + { + XmlWriter xw = null; + + var str = string.Empty; + + try + { + var settings = new XmlWriterSettings { OmitXmlDeclaration = true }; + var ns = new XmlSerializerNamespaces(); + ns.Add("", ""); + + using var sw = new StringWriter(CultureInfo.InvariantCulture); + + var xs = new XmlSerializer(typeof(SelectObjectOptions)); + using (xw = XmlWriter.Create(sw, settings)) + { + xs.Serialize(xw, this, ns); + xw.Flush(); + + str = sw.ToString(); + } + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + } + finally + { + xw?.Close(); + } + + return str; + } +} diff --git a/LibExternal/Minio/DataModel/Select/SelectObjectOutputSerialization.cs b/LibExternal/Minio/DataModel/Select/SelectObjectOutputSerialization.cs new file mode 100644 index 0000000..c663228 --- /dev/null +++ b/LibExternal/Minio/DataModel/Select/SelectObjectOutputSerialization.cs @@ -0,0 +1,27 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Select; + +[Serializable] +public class SelectObjectOutputSerialization +{ + [XmlElement("CSV")] public CSVOutputOptions CSV { get; set; } + + [XmlElement("JSON")] public JSONOutputOptions JSON { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Select/SelectObjectType.cs b/LibExternal/Minio/DataModel/Select/SelectObjectType.cs new file mode 100644 index 0000000..0f809ef --- /dev/null +++ b/LibExternal/Minio/DataModel/Select/SelectObjectType.cs @@ -0,0 +1,36 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Select; + +public sealed class SelectObjectType +{ + // Constants for JSONTypes. + public static readonly SelectObjectType CSV = new("CSV"); + public static readonly SelectObjectType JSON = new("JSON"); + public static readonly SelectObjectType Parquet = new("Parquet"); + + public SelectObjectType() + { + } + + public SelectObjectType(string type) + { + Type = type; + } + + public string Type { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Select/SelectResponseStream.cs b/LibExternal/Minio/DataModel/Select/SelectResponseStream.cs new file mode 100644 index 0000000..ded186c --- /dev/null +++ b/LibExternal/Minio/DataModel/Select/SelectResponseStream.cs @@ -0,0 +1,250 @@ +using System.Diagnostics.CodeAnalysis; +using System.IO.Hashing; +using System.Text; +using System.Xml.Serialization; +using Minio.Exceptions; +using Minio.Helper; + +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Select; + + + +[Serializable] +public sealed class SelectResponseStream : IDisposable +{ + private readonly Memory messageCRC = new byte[4]; + private readonly MemoryStream payloadStream; + private readonly Memory prelude = new byte[8]; + private readonly Memory preludeCRC = new byte[4]; + private bool disposed; + + private bool isProcessing; + + public SelectResponseStream() + { + } + + // SelectResponseStream is a struct for selectobjectcontent response. + public SelectResponseStream(Stream stream) + { + if (stream is not null) + { + var _ms = new MemoryStream(); + stream.CopyTo(_ms); + payloadStream = _ms; + Payload = new MemoryStream(); + } + + isProcessing = true; + _ = payloadStream.Seek(0, SeekOrigin.Begin); + Start(); + } + + public Stream Payload { get; private set; } + + [XmlElement("Stats", IsNullable = false)] + public StatsMessage Stats { get; set; } + + [XmlElement("Progress", IsNullable = false)] + public ProgressMessage Progress { get; set; } + + public void Dispose() + { + if (disposed) return; + + payloadStream?.Dispose(); + Payload?.Dispose(); + + Payload = null; + + disposed = true; + } + + private int ReadFromStream(Span buffer) + { + var read = -1; + if (!isProcessing) return read; + +#if NETSTANDARD + var bytes = new byte[buffer.Length]; + read + = payloadStream.Read(bytes, 0, buffer.Length); + bytes.CopyTo(buffer); +#else + read = payloadStream.Read(buffer); +#endif + if (!payloadStream.CanRead) isProcessing = false; + return read; + } + + [SuppressMessage("Design", "MA0051:Method is too long", Justification = "Needs to be refactored")] + private void Start() + { + var numBytesRead = 0; + while (isProcessing) + { + var n = ReadFromStream(prelude.Span); + numBytesRead += n; + n = ReadFromStream(preludeCRC.Span); + Span preludeCRCBytes = new byte[preludeCRC.Length]; + preludeCRC.Span.CopyTo(preludeCRCBytes); + if (BitConverter.IsLittleEndian) preludeCRCBytes.Reverse(); + numBytesRead += n; + Span inputArray = new byte[prelude.Length + 4]; + prelude.Span.CopyTo(inputArray[..prelude.Length]); + + var destinationPrelude = inputArray.Slice(inputArray.Length - 4, 4); + var isValidPrelude = Crc32.TryHash(inputArray[..^4], destinationPrelude, out _); + if (!isValidPrelude) throw new InvalidDataException("invalid prelude CRC: " + nameof(destinationPrelude)); + + if (!destinationPrelude.SequenceEqual(preludeCRCBytes)) + throw new InvalidDataException("Prelude CRC Mismatch: " + nameof(preludeCRCBytes)); + + var preludeBytes = prelude[..4].Span; + Span bytes = new byte[preludeBytes.Length]; + preludeBytes.CopyTo(bytes); + if (BitConverter.IsLittleEndian) bytes.Reverse(); + +#if NETSTANDARD + var totalLength = BitConverter.ToInt32(bytes.ToArray(), 0); +#else + var totalLength = BitConverter.ToInt32(bytes); +#endif + preludeBytes = prelude.Slice(4, 4).Span; + bytes = new byte[preludeBytes.Length]; + preludeBytes.CopyTo(bytes); + if (BitConverter.IsLittleEndian) bytes.Reverse(); + +#if NETSTANDARD + var headerLength = BitConverter.ToInt32(bytes.ToArray(), 0); +#else + var headerLength = BitConverter.ToInt32(bytes); +#endif + var payloadLength = totalLength - headerLength - 16; + + Span headers = new byte[headerLength]; + Memory payload = new byte[payloadLength]; + var num = ReadFromStream(headers); + if (num != headerLength) throw new IOException("insufficient data"); + num = ReadFromStream(payload.Span); + if (num != payloadLength) throw new IOException("insufficient data"); + + numBytesRead += num; + num = ReadFromStream(messageCRC.Span); + + var messageBytes = messageCRC.Span; + Span messageCRCBytes = new byte[messageBytes.Length]; + messageBytes.CopyTo(messageCRCBytes); + if (BitConverter.IsLittleEndian) messageCRCBytes.Reverse(); + // now verify message CRC + inputArray = new byte[totalLength]; + + prelude.Span.CopyTo(inputArray); + preludeCRC.Span.CopyTo(inputArray.Slice(prelude.Length, preludeCRC.Length)); + headers.CopyTo(inputArray.Slice(prelude.Length + preludeCRC.Length, headerLength)); + payload.Span.CopyTo(inputArray.Slice(prelude.Length + preludeCRC.Length + headerLength, payloadLength)); + + var destinationMessage = inputArray.Slice(inputArray.Length - 4, 4); + var isValidMessage = Crc32.TryHash(inputArray[..^4], destinationMessage, out _); + if (!isValidMessage) throw new InvalidDataException("invalid message CRC: " + nameof(destinationMessage)); + + if (!destinationMessage.SequenceEqual(messageCRCBytes)) + throw new InvalidDataException("message CRC Mismatch: " + nameof(messageCRCBytes)); + + var headerMap = ExtractHeaders(headers); + + if (headerMap.TryGetValue(":message-type", out var value)) + if (value.Equals(":error", StringComparison.OrdinalIgnoreCase)) + { + headerMap.TryGetValue(":error-code", out var errorCode); + headerMap.TryGetValue(":error-message", out var errorMessage); + throw new SelectObjectContentException(errorCode + ":" + errorMessage); + } + + if (headerMap.TryGetValue(":event-type", out value)) + { + if (value.Equals("End", StringComparison.OrdinalIgnoreCase)) + { + // throw new UnexpectedShortReadException("Insufficient data"); + isProcessing = false; + break; + } + + if (value.Equals("Cont", StringComparison.OrdinalIgnoreCase) || payloadLength < 1) continue; + if (value.Equals("Progress", StringComparison.OrdinalIgnoreCase)) + { + var progress = new ProgressMessage(); + using var stream = new MemoryStream(payload.ToArray()); + progress = Utils.DeserializeXml(stream); + + Progress = progress; + } + + if (value.Equals("Stats", StringComparison.OrdinalIgnoreCase)) + { + var stats = new StatsMessage(); + using var stream = new MemoryStream(payload.ToArray()); + stats = Utils.DeserializeXml(stream); + + Stats = stats; + } + + if (value.Equals("Records", StringComparison.OrdinalIgnoreCase)) Payload.Write(payload.Span); + } + } + + isProcessing = false; + Payload.Seek(0, SeekOrigin.Begin); + payloadStream.Close(); + } + + private Dictionary ExtractHeaders(Span data) + { + var headerMap = new Dictionary(StringComparer.Ordinal); + var offset = 0; + + while (offset < data.Length) + { + var nameLength = data[offset++]; + var b = data.Slice(offset, nameLength); + + var name = Encoding.UTF8.GetString(b); + + offset += nameLength; + var hdrValue = data[offset++]; + if (hdrValue != 7) throw new IOException("header value type is not 7"); + b = data.Slice(offset, 2); + if (BitConverter.IsLittleEndian) b.Reverse(); + offset += 2; + +#if NETSTANDARD + int headerValLength = BitConverter.ToInt16(b.ToArray(), 0); +#else + int headerValLength = BitConverter.ToInt16(b); +#endif + b = data.Slice(offset, headerValLength); + + var value = Encoding.UTF8.GetString(b); + offset += headerValLength; + headerMap.Add(name, value); + } + + return headerMap; + } +} diff --git a/LibExternal/Minio/DataModel/Select/StatsMessage.cs b/LibExternal/Minio/DataModel/Select/StatsMessage.cs new file mode 100644 index 0000000..f24d9b3 --- /dev/null +++ b/LibExternal/Minio/DataModel/Select/StatsMessage.cs @@ -0,0 +1,31 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +// StatsMessage is a struct for stat xml message. +namespace Minio.DataModel.Select; + +[Serializable] +[XmlRoot(ElementName = "Stats")] +public class StatsMessage +{ + [XmlElement] public long BytesScanned { get; set; } + + [XmlElement] public long BytesProcessed { get; set; } + + [XmlElement] public long BytesReturned { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Tags/Tag.cs b/LibExternal/Minio/DataModel/Tags/Tag.cs new file mode 100644 index 0000000..1ef8ac4 --- /dev/null +++ b/LibExternal/Minio/DataModel/Tags/Tag.cs @@ -0,0 +1,38 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel.Tags; + +[Serializable] +[XmlRoot(ElementName = "Tag")] +public class Tag +{ + public Tag() + { + } + + public Tag(string key, string value) + { + Key = key; + Value = value; + } + + [XmlElement("Key")] public string Key { get; set; } + + [XmlElement("Value")] public string Value { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Tags/TagSet.cs b/LibExternal/Minio/DataModel/Tags/TagSet.cs new file mode 100644 index 0000000..d546ee8 --- /dev/null +++ b/LibExternal/Minio/DataModel/Tags/TagSet.cs @@ -0,0 +1,39 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Collections.ObjectModel; +using System.Xml.Serialization; + +namespace Minio.DataModel.Tags; + +[Serializable] +[XmlRoot(ElementName = "TagSet")] +public class TagSet +{ + public TagSet() + { + Tag = null; + } + + public TagSet(IDictionary tags) + { + if (tags is null || tags.Count == 0) return; + Tag = new Collection(); + foreach (var item in tags) Tag.Add(new Tag(item.Key, item.Value)); + } + + [XmlElement("Tag")] public Collection Tag { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Tags/Tagging.cs b/LibExternal/Minio/DataModel/Tags/Tagging.cs new file mode 100644 index 0000000..9053948 --- /dev/null +++ b/LibExternal/Minio/DataModel/Tags/Tagging.cs @@ -0,0 +1,164 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Globalization; +using System.Xml; +using System.Xml.Serialization; +using Minio.Helper; + +namespace Minio.DataModel.Tags; + +[Serializable] +[XmlRoot(ElementName = "Tagging")] +/* + * References for Tagging. + * https://docs.aws.amazon.com/AmazonS3/latest/dev/object-tagging.html + * https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Tags.html#tag-restrictions + */ +public class Tagging +{ + internal const uint MAX_TAG_COUNT_PER_RESOURCE = 50; + internal const uint MAX_TAG_COUNT_PER_OBJECT = 10; + internal const uint MAX_TAG_KEY_LENGTH = 128; + internal const uint MAX_TAG_VALUE_LENGTH = 256; + + public Tagging() + { + TaggingSet = null; + } + + public Tagging(IDictionary tags, bool isObjects) + { + if (tags is null) + { + TaggingSet = null; + return; + } + + var tagging_upper_limit = isObjects ? MAX_TAG_COUNT_PER_OBJECT : MAX_TAG_COUNT_PER_RESOURCE; + if (tags.Count > tagging_upper_limit) + throw new ArgumentOutOfRangeException(nameof(tags) + ". Count of tags exceeds maximum limit allowed for " + + (isObjects ? "objects." : "buckets.")); + + foreach (var tag in tags) + { + if (!ValidateTagKey(tag.Key)) + throw new InvalidOperationException("Invalid Tagging key " + tag.Key); + if (!ValidateTagValue(tag.Value)) + throw new InvalidOperationException("Invalid Tagging value " + tag.Value); + } + + TaggingSet = new TagSet(tags); + } + + [XmlElement("TagSet")] public TagSet TaggingSet { get; set; } + + [XmlIgnore] + public IDictionary Tags + { + get + { + if (TaggingSet is null || TaggingSet.Tag.Count == 0) return null; + var tagMap = new Dictionary(StringComparer.Ordinal); + foreach (var tag in TaggingSet.Tag) tagMap[tag.Key] = tag.Value; + return tagMap; + } + } + + internal bool ValidateTagKey(string key) + { + if (string.IsNullOrEmpty(key) || + string.IsNullOrWhiteSpace(key) || + key.Length > MAX_TAG_KEY_LENGTH || + key.Contains('&', StringComparison.Ordinal)) + return false; + + return true; + } + + internal bool ValidateTagValue(string value) + { + if (value is null || // Empty or whitespace is allowed + value.Length > MAX_TAG_VALUE_LENGTH || + value.Contains('&', StringComparison.OrdinalIgnoreCase)) + return false; + + return true; + } + + public string MarshalXML() + { + XmlWriter xw = null; + + var str = string.Empty; + + try + { + var settings = new XmlWriterSettings { OmitXmlDeclaration = true }; + var ns = new XmlSerializerNamespaces(); + ns.Add(string.Empty, string.Empty); + + using var sw = new StringWriter(CultureInfo.InvariantCulture); + + var xs = new XmlSerializer(typeof(Tagging), ""); + using (xw = XmlWriter.Create(sw, settings)) + { + xs.Serialize(xw, this, ns); + xw.Flush(); + str = Utils.RemoveNamespaceInXML(sw.ToString()); + } + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + } + finally + { + xw?.Close(); + } + + return str; + } + + public static Tagging GetBucketTags(IDictionary tags) + { + return new Tagging(tags, false); + } + + public static Tagging GetObjectTags(IDictionary tags) + { + return new Tagging(tags, true); + } + + internal string GetTagString() + { + if (TaggingSet is null || (TaggingSet.Tag is null && TaggingSet.Tag.Count == 0)) return null; + var tagStr = ""; + var i = 0; + foreach (var tag in TaggingSet.Tag) + { + var append = i++ < TaggingSet.Tag.Count - 1 ? "&" : ""; + tagStr += tag.Key + "=" + tag.Value + append; + } + + return tagStr; + } + + public override string ToString() + { + return GetTagString(); + } +} diff --git a/LibExternal/Minio/DataModel/Tracing/RequestParameter.cs b/LibExternal/Minio/DataModel/Tracing/RequestParameter.cs new file mode 100644 index 0000000..c2e8f99 --- /dev/null +++ b/LibExternal/Minio/DataModel/Tracing/RequestParameter.cs @@ -0,0 +1,24 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Tracing; + +public sealed class RequestParameter +{ + public string Name { get; internal set; } + public object Value { get; internal set; } + public string Type { get; internal set; } +} diff --git a/LibExternal/Minio/DataModel/Tracing/RequestToLog.cs b/LibExternal/Minio/DataModel/Tracing/RequestToLog.cs new file mode 100644 index 0000000..ab03aca --- /dev/null +++ b/LibExternal/Minio/DataModel/Tracing/RequestToLog.cs @@ -0,0 +1,25 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel.Tracing; + +public sealed class RequestToLog +{ + public string Resource { get; internal set; } + public IEnumerable Parameters { get; internal set; } + public string Method { get; internal set; } + public Uri Uri { get; internal set; } +} diff --git a/LibExternal/Minio/DataModel/Tracing/ResponseToLog.cs b/LibExternal/Minio/DataModel/Tracing/ResponseToLog.cs new file mode 100644 index 0000000..434d8b6 --- /dev/null +++ b/LibExternal/Minio/DataModel/Tracing/ResponseToLog.cs @@ -0,0 +1,34 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Diagnostics.CodeAnalysis; +using System.Net; + +namespace Minio.DataModel.Tracing; + +public sealed class ResponseToLog +{ + public string Content { get; internal set; } + + [SuppressMessage("Design", "MA0016:Prefer returning collection abstraction instead of implementation", + Justification = "Needs to be concrete type for XML deserialization")] + public Dictionary Headers { get; internal set; } + + public HttpStatusCode StatusCode { get; internal set; } + public Uri ResponseUri { get; internal set; } + public double DurationMs { get; internal set; } + public string ErrorMessage { get; set; } +} diff --git a/LibExternal/Minio/DataModel/Upload.cs b/LibExternal/Minio/DataModel/Upload.cs new file mode 100644 index 0000000..b67d900 --- /dev/null +++ b/LibExternal/Minio/DataModel/Upload.cs @@ -0,0 +1,24 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.DataModel; + +public class Upload +{ + public string Key { get; set; } + public string UploadId { get; set; } + public string Initiated { get; set; } +} diff --git a/LibExternal/Minio/DataModel/VersioningConfiguration.cs b/LibExternal/Minio/DataModel/VersioningConfiguration.cs new file mode 100644 index 0000000..f3ec6b4 --- /dev/null +++ b/LibExternal/Minio/DataModel/VersioningConfiguration.cs @@ -0,0 +1,50 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.DataModel; + +[Serializable] +[XmlRoot(ElementName = "VersioningConfiguration", Namespace = "http://s3.amazonaws.com/doc/2006-03-01/")] +public class VersioningConfiguration +{ + public VersioningConfiguration() + { + Status = "Off"; + MfaDelete = "Disabled"; + } + + public VersioningConfiguration(bool enableVersioning = true) + { + if (enableVersioning) + Status = "Enabled"; + else + Status = "Suspended"; + } + + public VersioningConfiguration(VersioningConfiguration vc) + { + if (vc is null) throw new ArgumentNullException(nameof(vc)); + + Status = vc.Status; + MfaDelete = vc.MfaDelete; + } + + [XmlElement] public string Status { get; set; } + + public string MfaDelete { get; set; } +} diff --git a/LibExternal/Minio/Exceptions/AccessDeniedException.cs b/LibExternal/Minio/Exceptions/AccessDeniedException.cs new file mode 100644 index 0000000..87e6dc2 --- /dev/null +++ b/LibExternal/Minio/Exceptions/AccessDeniedException.cs @@ -0,0 +1,50 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2017, 2018, 2019, 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class AccessDeniedException : MinioException +{ + public AccessDeniedException(string message) : base(message) + { + } + + public AccessDeniedException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public AccessDeniedException(string message, ResponseResult serverResponse) : base(message, serverResponse) + { + } + + public AccessDeniedException() + { + } + + public AccessDeniedException(string message, Exception innerException) : base(message, innerException) + { + } + + protected AccessDeniedException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base( + serializationInfo, streamingContext) + { + } +} diff --git a/LibExternal/Minio/Exceptions/AuthorizationException.cs b/LibExternal/Minio/Exceptions/AuthorizationException.cs new file mode 100644 index 0000000..92904e1 --- /dev/null +++ b/LibExternal/Minio/Exceptions/AuthorizationException.cs @@ -0,0 +1,53 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2017, 2018, 2019, 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; + +namespace Minio.Exceptions; + +[Serializable] +public class AuthorizationException : Exception +{ + internal readonly string accessKey; + internal readonly string bucketName; + internal readonly string resource; + + public AuthorizationException() + { + } + + public AuthorizationException(string message) : base(message) + { + } + + public AuthorizationException(string resource, string bucketName, string message, string accesskey = null) : + base(message) + { + this.resource = resource; + this.bucketName = bucketName; + accessKey = accesskey; + } + + public AuthorizationException(string message, Exception innerException) : base(message, innerException) + { + } + + protected AuthorizationException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base( + serializationInfo, streamingContext) + { + } +} diff --git a/LibExternal/Minio/Exceptions/BucketNotFoundException.cs b/LibExternal/Minio/Exceptions/BucketNotFoundException.cs new file mode 100644 index 0000000..5cb9d94 --- /dev/null +++ b/LibExternal/Minio/Exceptions/BucketNotFoundException.cs @@ -0,0 +1,62 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2017, 2018, 2019, 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class BucketNotFoundException : MinioException +{ + private readonly string bucketName; + + public BucketNotFoundException(string bucketName, string message) : base(message) + { + this.bucketName = bucketName; + } + + public BucketNotFoundException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public BucketNotFoundException(string message) : base(message) + { + } + + public BucketNotFoundException(string message, ResponseResult serverResponse) : base(message, serverResponse) + { + } + + public BucketNotFoundException() + { + } + + public BucketNotFoundException(string message, Exception innerException) : base(message, innerException) + { + } + + protected BucketNotFoundException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base( + serializationInfo, streamingContext) + { + } + + public override string ToString() + { + return $"{bucketName}: {base.ToString()}"; + } +} diff --git a/LibExternal/Minio/Exceptions/ConnectionException.cs b/LibExternal/Minio/Exceptions/ConnectionException.cs new file mode 100644 index 0000000..b33bc10 --- /dev/null +++ b/LibExternal/Minio/Exceptions/ConnectionException.cs @@ -0,0 +1,50 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2017, 2018, 2019, 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class ConnectionException : MinioException +{ + public ConnectionException(string message, ResponseResult response) : base(message, response) + { + } + + public ConnectionException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public ConnectionException(string message) : base(message) + { + } + + public ConnectionException() + { + } + + public ConnectionException(string message, Exception innerException) : base(message, innerException) + { + } + + protected ConnectionException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base( + serializationInfo, streamingContext) + { + } +} diff --git a/LibExternal/Minio/Exceptions/CredentialsProviderException.cs b/LibExternal/Minio/Exceptions/CredentialsProviderException.cs new file mode 100644 index 0000000..dd61131 --- /dev/null +++ b/LibExternal/Minio/Exceptions/CredentialsProviderException.cs @@ -0,0 +1,61 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class CredentialsProviderException : MinioException +{ + private readonly string credentialProviderType; + + public CredentialsProviderException(string credentialProviderType, string message) : base(message) + { + this.credentialProviderType = credentialProviderType; + } + + public CredentialsProviderException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public CredentialsProviderException(string message) : base(message) + { + } + + public CredentialsProviderException(string message, ResponseResult serverResponse) : base(message, serverResponse) + { + } + + public CredentialsProviderException() + { + } + + public CredentialsProviderException(string message, Exception innerException) : base(message, innerException) + { + } + + protected CredentialsProviderException(SerializationInfo serializationInfo, StreamingContext streamingContext) : + base(serializationInfo, streamingContext) + { + } + + public override string ToString() + { + return $"{credentialProviderType}: {base.ToString()}"; + } +} diff --git a/LibExternal/Minio/Exceptions/DeleteError.cs b/LibExternal/Minio/Exceptions/DeleteError.cs new file mode 100644 index 0000000..6a96e35 --- /dev/null +++ b/LibExternal/Minio/Exceptions/DeleteError.cs @@ -0,0 +1,25 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.Exceptions; + +[Serializable] +[XmlRoot(ElementName = "Error")] +public class DeleteError : ErrorResponse +{ +} diff --git a/LibExternal/Minio/Exceptions/DeleteObjectException.cs b/LibExternal/Minio/Exceptions/DeleteObjectException.cs new file mode 100644 index 0000000..61f7715 --- /dev/null +++ b/LibExternal/Minio/Exceptions/DeleteObjectException.cs @@ -0,0 +1,49 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class DeleteObjectException : MinioException +{ + public DeleteObjectException(string message) : base(message) + { + } + + public DeleteObjectException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public DeleteObjectException(string message, ResponseResult serverResponse) : base(message, serverResponse) + { + } + + public DeleteObjectException() + { + } + + public DeleteObjectException(string message, Exception innerException) : base(message, innerException) + { + } + + protected DeleteObjectException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base( + serializationInfo, streamingContext) + { + } +} diff --git a/LibExternal/Minio/Exceptions/EntityTooLargeException.cs b/LibExternal/Minio/Exceptions/EntityTooLargeException.cs new file mode 100644 index 0000000..dea0cde --- /dev/null +++ b/LibExternal/Minio/Exceptions/EntityTooLargeException.cs @@ -0,0 +1,49 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class EntityTooLargeException : MinioException +{ + public EntityTooLargeException(string message) : base(message) + { + } + + public EntityTooLargeException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public EntityTooLargeException(string message, ResponseResult serverResponse) : base(message, serverResponse) + { + } + + public EntityTooLargeException() + { + } + + public EntityTooLargeException(string message, Exception innerException) : base(message, innerException) + { + } + + protected EntityTooLargeException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base( + serializationInfo, streamingContext) + { + } +} diff --git a/LibExternal/Minio/Exceptions/ErrorResponse.cs b/LibExternal/Minio/Exceptions/ErrorResponse.cs new file mode 100644 index 0000000..4ccbd9e --- /dev/null +++ b/LibExternal/Minio/Exceptions/ErrorResponse.cs @@ -0,0 +1,35 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml.Serialization; + +namespace Minio.Exceptions; + +[Serializable] +[XmlRoot(ElementName = "Error", Namespace = "")] +public class ErrorResponse +{ + public string Code { get; set; } + public string Message { get; set; } + public string RequestId { get; set; } + public string HostId { get; set; } + public string Resource { get; set; } + public string BucketName { get; set; } + public string Key { get; set; } + public string VersionId { get; set; } + public bool DeleteMarker { get; set; } + public string BucketRegion { get; set; } +} diff --git a/LibExternal/Minio/Exceptions/ErrorResponseException.cs b/LibExternal/Minio/Exceptions/ErrorResponseException.cs new file mode 100644 index 0000000..632883f --- /dev/null +++ b/LibExternal/Minio/Exceptions/ErrorResponseException.cs @@ -0,0 +1,56 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2017, 2018, 2019, 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class ErrorResponseException : MinioException +{ + public ErrorResponseException(ErrorResponse errorResponse, ResponseResult serverResponse) : + base(serverResponse) + { + Response = errorResponse; + } + + public ErrorResponseException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public ErrorResponseException(string message) : base(message) + { + } + + public ErrorResponseException(string message, ResponseResult serverResponse) : base(message, serverResponse) + { + } + + public ErrorResponseException() + { + } + + public ErrorResponseException(string message, Exception innerException) : base(message, innerException) + { + } + + protected ErrorResponseException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base( + serializationInfo, streamingContext) + { + } +} diff --git a/LibExternal/Minio/Exceptions/ForbiddenException.cs b/LibExternal/Minio/Exceptions/ForbiddenException.cs new file mode 100644 index 0000000..2f30f74 --- /dev/null +++ b/LibExternal/Minio/Exceptions/ForbiddenException.cs @@ -0,0 +1,49 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class ForbiddenException : MinioException +{ + public ForbiddenException(string message) : base(message) + { + } + + public ForbiddenException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public ForbiddenException(string message, ResponseResult serverResponse) : base(message, serverResponse) + { + } + + public ForbiddenException() + { + } + + public ForbiddenException(string message, Exception innerException) : base(message, innerException) + { + } + + protected ForbiddenException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base( + serializationInfo, streamingContext) + { + } +} diff --git a/LibExternal/Minio/Exceptions/InternalClientException.cs b/LibExternal/Minio/Exceptions/InternalClientException.cs new file mode 100644 index 0000000..6cf767c --- /dev/null +++ b/LibExternal/Minio/Exceptions/InternalClientException.cs @@ -0,0 +1,50 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2017, 2018, 2019, 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class InternalClientException : MinioException +{ + public InternalClientException(string message, ResponseResult response) : base(message, response) + { + } + + public InternalClientException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public InternalClientException(string message) : base(message) + { + } + + public InternalClientException() + { + } + + public InternalClientException(string message, Exception innerException) : base(message, innerException) + { + } + + protected InternalClientException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base( + serializationInfo, streamingContext) + { + } +} diff --git a/LibExternal/Minio/Exceptions/InternalServerException.cs b/LibExternal/Minio/Exceptions/InternalServerException.cs new file mode 100644 index 0000000..d8ac855 --- /dev/null +++ b/LibExternal/Minio/Exceptions/InternalServerException.cs @@ -0,0 +1,49 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class InternalServerException : MinioException +{ + public InternalServerException(string message) : base(message) + { + } + + public InternalServerException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public InternalServerException(string message, ResponseResult serverResponse) : base(message, serverResponse) + { + } + + public InternalServerException() + { + } + + public InternalServerException(string message, Exception innerException) : base(message, innerException) + { + } + + protected InternalServerException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base( + serializationInfo, streamingContext) + { + } +} diff --git a/LibExternal/Minio/Exceptions/InvalidBucketNameException.cs b/LibExternal/Minio/Exceptions/InvalidBucketNameException.cs new file mode 100644 index 0000000..354c3a2 --- /dev/null +++ b/LibExternal/Minio/Exceptions/InvalidBucketNameException.cs @@ -0,0 +1,61 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class InvalidBucketNameException : MinioException +{ + private readonly string bucketName; + + public InvalidBucketNameException(string bucketName, string message) : base(message) + { + this.bucketName = bucketName; + } + + public InvalidBucketNameException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public InvalidBucketNameException(string message) : base(message) + { + } + + public InvalidBucketNameException(string message, ResponseResult serverResponse) : base(message, serverResponse) + { + } + + public InvalidBucketNameException() + { + } + + public InvalidBucketNameException(string message, Exception innerException) : base(message, innerException) + { + } + + protected InvalidBucketNameException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base( + serializationInfo, streamingContext) + { + } + + public override string ToString() + { + return $"{bucketName}: {base.ToString()}"; + } +} diff --git a/LibExternal/Minio/Exceptions/InvalidContentLengthException.cs b/LibExternal/Minio/Exceptions/InvalidContentLengthException.cs new file mode 100644 index 0000000..778d1c5 --- /dev/null +++ b/LibExternal/Minio/Exceptions/InvalidContentLengthException.cs @@ -0,0 +1,63 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class InvalidContentLengthException : MinioException +{ + private readonly string bucketName; + private readonly string objectName; + + public InvalidContentLengthException(string bucketName, string objectName, string message) : base(message) + { + this.bucketName = bucketName; + this.objectName = objectName; + } + + public InvalidContentLengthException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public InvalidContentLengthException(string message) : base(message) + { + } + + public InvalidContentLengthException(string message, ResponseResult serverResponse) : base(message, serverResponse) + { + } + + public InvalidContentLengthException() + { + } + + public InvalidContentLengthException(string message, Exception innerException) : base(message, innerException) + { + } + + protected InvalidContentLengthException(SerializationInfo serializationInfo, StreamingContext streamingContext) : + base(serializationInfo, streamingContext) + { + } + + public override string ToString() + { + return $"{bucketName} :{objectName}: {base.ToString()}"; + } +} diff --git a/LibExternal/Minio/Exceptions/InvalidEndpointException.cs b/LibExternal/Minio/Exceptions/InvalidEndpointException.cs new file mode 100644 index 0000000..9a35a0d --- /dev/null +++ b/LibExternal/Minio/Exceptions/InvalidEndpointException.cs @@ -0,0 +1,63 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class InvalidEndpointException : MinioException +{ + private readonly string endpoint; + + public InvalidEndpointException(string endpoint, string message) : base(message) + { + this.endpoint = endpoint; + } + + public InvalidEndpointException(string message) : base(message) + { + } + + public InvalidEndpointException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public InvalidEndpointException(string message, ResponseResult serverResponse) : base(message, serverResponse) + { + } + + public InvalidEndpointException() + { + } + + public InvalidEndpointException(string message, Exception innerException) : base(message, innerException) + { + } + + protected InvalidEndpointException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base( + serializationInfo, streamingContext) + { + } + + public override string ToString() + { + if (string.IsNullOrEmpty(endpoint)) + return base.ToString(); + return $"{endpoint}: {base.ToString()}"; + } +} diff --git a/LibExternal/Minio/Exceptions/InvalidExpiryRangeException.cs b/LibExternal/Minio/Exceptions/InvalidExpiryRangeException.cs new file mode 100644 index 0000000..a770ffb --- /dev/null +++ b/LibExternal/Minio/Exceptions/InvalidExpiryRangeException.cs @@ -0,0 +1,49 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class InvalidExpiryRangeException : MinioException +{ + public InvalidExpiryRangeException(string message) : base(message) + { + } + + public InvalidExpiryRangeException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public InvalidExpiryRangeException(string message, ResponseResult serverResponse) : base(message, serverResponse) + { + } + + public InvalidExpiryRangeException() + { + } + + public InvalidExpiryRangeException(string message, Exception innerException) : base(message, innerException) + { + } + + protected InvalidExpiryRangeException(SerializationInfo serializationInfo, StreamingContext streamingContext) : + base(serializationInfo, streamingContext) + { + } +} diff --git a/LibExternal/Minio/Exceptions/InvalidObjectNameException.cs b/LibExternal/Minio/Exceptions/InvalidObjectNameException.cs new file mode 100644 index 0000000..915a2e3 --- /dev/null +++ b/LibExternal/Minio/Exceptions/InvalidObjectNameException.cs @@ -0,0 +1,61 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class InvalidObjectNameException : MinioException +{ + private readonly string objectName; + + public InvalidObjectNameException(string objectName, string message) : base(message) + { + this.objectName = objectName; + } + + public InvalidObjectNameException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public InvalidObjectNameException(string message) : base(message) + { + } + + public InvalidObjectNameException(string message, ResponseResult serverResponse) : base(message, serverResponse) + { + } + + public InvalidObjectNameException() + { + } + + public InvalidObjectNameException(string message, Exception innerException) : base(message, innerException) + { + } + + protected InvalidObjectNameException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base( + serializationInfo, streamingContext) + { + } + + public override string ToString() + { + return $"{objectName}: {base.ToString()}"; + } +} diff --git a/LibExternal/Minio/Exceptions/InvalidObjectPrefixException.cs b/LibExternal/Minio/Exceptions/InvalidObjectPrefixException.cs new file mode 100644 index 0000000..c05327a --- /dev/null +++ b/LibExternal/Minio/Exceptions/InvalidObjectPrefixException.cs @@ -0,0 +1,61 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class InvalidObjectPrefixException : MinioException +{ + private readonly string objectPrefix; + + public InvalidObjectPrefixException(string objectPrefix, string message) : base(message) + { + this.objectPrefix = objectPrefix; + } + + public InvalidObjectPrefixException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public InvalidObjectPrefixException(string message) : base(message) + { + } + + public InvalidObjectPrefixException(string message, ResponseResult serverResponse) : base(message, serverResponse) + { + } + + public InvalidObjectPrefixException() + { + } + + public InvalidObjectPrefixException(string message, Exception innerException) : base(message, innerException) + { + } + + protected InvalidObjectPrefixException(SerializationInfo serializationInfo, StreamingContext streamingContext) : + base(serializationInfo, streamingContext) + { + } + + public override string ToString() + { + return $"{objectPrefix}: {base.ToString()}"; + } +} diff --git a/LibExternal/Minio/Exceptions/MalFormedXMLException.cs b/LibExternal/Minio/Exceptions/MalFormedXMLException.cs new file mode 100644 index 0000000..ce63683 --- /dev/null +++ b/LibExternal/Minio/Exceptions/MalFormedXMLException.cs @@ -0,0 +1,53 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2017, 2018, 2019, 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; + +namespace Minio.Exceptions; + +[Serializable] +public class MalFormedXMLException : Exception +{ + internal string bucketName; + internal string key; + internal string resource; + + public MalFormedXMLException() + { + } + + public MalFormedXMLException(string message) : base(message) + { + } + + public MalFormedXMLException(string message, Exception innerException) : base(message, innerException) + { + } + + public MalFormedXMLException(string resource, string bucketName, string message, string keyName = null) : + base(message) + { + this.resource = resource; + this.bucketName = bucketName; + key = keyName; + } + + protected MalFormedXMLException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base( + serializationInfo, streamingContext) + { + } +} diff --git a/LibExternal/Minio/Exceptions/MinioException.cs b/LibExternal/Minio/Exceptions/MinioException.cs new file mode 100644 index 0000000..d3ad310 --- /dev/null +++ b/LibExternal/Minio/Exceptions/MinioException.cs @@ -0,0 +1,79 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2017, 2018, 2019, 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class MinioException : Exception +{ + public MinioException() + { + } + + public MinioException(string message, Exception innerException) : base(message, innerException) + { + } + + public MinioException(ResponseResult serverResponse) : this(null, serverResponse) + { + } + + public MinioException(string message) : this(message, serverResponse: null) + { + } + + protected MinioException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base( + serializationInfo, streamingContext) + { + } + + public MinioException(string message, ResponseResult serverResponse) + : base(GetMessage(message, serverResponse)) + { + ServerMessage = message; + ServerResponse = serverResponse; + } + + public string ServerMessage { get; } + + public ResponseResult ServerResponse { get; } + + public ErrorResponse Response { get; internal set; } + + public string XmlError { get; internal set; } + + private static string GetMessage(string message, ResponseResult serverResponse) + { + if (serverResponse is null && string.IsNullOrEmpty(message)) + throw new ArgumentNullException(nameof(message)); + + if (serverResponse is null) + return $"MinIO API responded with message={message}"; + + var contentString = serverResponse.Content; + + if (message is null) + return + $"MinIO API responded with status code={serverResponse.StatusCode}, response={serverResponse.ErrorMessage}, content={contentString}"; + + return + $"MinIO API responded with message={message}. Status code={serverResponse.StatusCode}, response={serverResponse.ErrorMessage}, content={contentString}"; + } +} diff --git a/LibExternal/Minio/Exceptions/MissingBucketReplicationConfigurationException.cs b/LibExternal/Minio/Exceptions/MissingBucketReplicationConfigurationException.cs new file mode 100644 index 0000000..1c87f22 --- /dev/null +++ b/LibExternal/Minio/Exceptions/MissingBucketReplicationConfigurationException.cs @@ -0,0 +1,63 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class MissingBucketReplicationConfigurationException : MinioException +{ + private readonly string bucketName; + + public MissingBucketReplicationConfigurationException(string bucketName, string message) : base(message) + { + this.bucketName = bucketName; + } + + public MissingBucketReplicationConfigurationException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public MissingBucketReplicationConfigurationException(string message) : base(message) + { + } + + public MissingBucketReplicationConfigurationException(string message, ResponseResult serverResponse) : base(message, + serverResponse) + { + } + + public MissingBucketReplicationConfigurationException() + { + } + + public MissingBucketReplicationConfigurationException(string message, Exception innerException) : base(message, + innerException) + { + } + + protected MissingBucketReplicationConfigurationException(SerializationInfo serializationInfo, + StreamingContext streamingContext) : base(serializationInfo, streamingContext) + { + } + + public override string ToString() + { + return $"{bucketName}: {base.ToString()}"; + } +} diff --git a/LibExternal/Minio/Exceptions/MissingObjectLockConfigurationException.cs b/LibExternal/Minio/Exceptions/MissingObjectLockConfigurationException.cs new file mode 100644 index 0000000..6682fa5 --- /dev/null +++ b/LibExternal/Minio/Exceptions/MissingObjectLockConfigurationException.cs @@ -0,0 +1,63 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class MissingObjectLockConfigurationException : MinioException +{ + private readonly string bucketName; + + public MissingObjectLockConfigurationException(string bucketName, string message) : base(message) + { + this.bucketName = bucketName; + } + + public MissingObjectLockConfigurationException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public MissingObjectLockConfigurationException(string message) : base(message) + { + } + + public MissingObjectLockConfigurationException(string message, ResponseResult serverResponse) : base(message, + serverResponse) + { + } + + public MissingObjectLockConfigurationException() + { + } + + public MissingObjectLockConfigurationException(string message, Exception innerException) : base(message, + innerException) + { + } + + protected MissingObjectLockConfigurationException(SerializationInfo serializationInfo, + StreamingContext streamingContext) : base(serializationInfo, streamingContext) + { + } + + public override string ToString() + { + return $"{bucketName}: {base.ToString()}"; + } +} diff --git a/LibExternal/Minio/Exceptions/ObjectNotFoundException.cs b/LibExternal/Minio/Exceptions/ObjectNotFoundException.cs new file mode 100644 index 0000000..9322bc3 --- /dev/null +++ b/LibExternal/Minio/Exceptions/ObjectNotFoundException.cs @@ -0,0 +1,61 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class ObjectNotFoundException : MinioException +{ + private readonly string objectName; + + public ObjectNotFoundException(string objectName, string message) : base(message) + { + this.objectName = objectName; + } + + public ObjectNotFoundException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public ObjectNotFoundException(string message) : base(message) + { + } + + public ObjectNotFoundException(string message, ResponseResult serverResponse) : base(message, serverResponse) + { + } + + public ObjectNotFoundException() + { + } + + public ObjectNotFoundException(string message, Exception innerException) : base(message, innerException) + { + } + + protected ObjectNotFoundException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base( + serializationInfo, streamingContext) + { + } + + public override string ToString() + { + return $"{objectName}: {base.ToString()}"; + } +} diff --git a/LibExternal/Minio/Exceptions/RedirectionException.cs b/LibExternal/Minio/Exceptions/RedirectionException.cs new file mode 100644 index 0000000..3f4fb81 --- /dev/null +++ b/LibExternal/Minio/Exceptions/RedirectionException.cs @@ -0,0 +1,49 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class RedirectionException : MinioException +{ + public RedirectionException(string message) : base(message) + { + } + + public RedirectionException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public RedirectionException(string message, ResponseResult serverResponse) : base(message, serverResponse) + { + } + + public RedirectionException() + { + } + + public RedirectionException(string message, Exception innerException) : base(message, innerException) + { + } + + protected RedirectionException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base( + serializationInfo, streamingContext) + { + } +} diff --git a/LibExternal/Minio/Exceptions/SelectObjectContentException.cs b/LibExternal/Minio/Exceptions/SelectObjectContentException.cs new file mode 100644 index 0000000..1adbead --- /dev/null +++ b/LibExternal/Minio/Exceptions/SelectObjectContentException.cs @@ -0,0 +1,50 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2017, 2018, 2019, 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class SelectObjectContentException : MinioException +{ + public SelectObjectContentException(string message) : base(message) + { + } + + public SelectObjectContentException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public SelectObjectContentException(string message, ResponseResult serverResponse) : base(message, serverResponse) + { + } + + public SelectObjectContentException() + { + } + + public SelectObjectContentException(string message, Exception innerException) : base(message, innerException) + { + } + + protected SelectObjectContentException(SerializationInfo serializationInfo, StreamingContext streamingContext) : + base(serializationInfo, streamingContext) + { + } +} diff --git a/LibExternal/Minio/Exceptions/UnexpectedMinioException.cs b/LibExternal/Minio/Exceptions/UnexpectedMinioException.cs new file mode 100644 index 0000000..d6d1a76 --- /dev/null +++ b/LibExternal/Minio/Exceptions/UnexpectedMinioException.cs @@ -0,0 +1,50 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2017, 2018, 2019, 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class UnexpectedMinioException : MinioException +{ + public UnexpectedMinioException(string message) : base(message) + { + } + + public UnexpectedMinioException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public UnexpectedMinioException(string message, ResponseResult serverResponse) : base(message, serverResponse) + { + } + + public UnexpectedMinioException() + { + } + + public UnexpectedMinioException(string message, Exception innerException) : base(message, innerException) + { + } + + protected UnexpectedMinioException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base( + serializationInfo, streamingContext) + { + } +} diff --git a/LibExternal/Minio/Exceptions/UnexpectedShortReadException.cs b/LibExternal/Minio/Exceptions/UnexpectedShortReadException.cs new file mode 100644 index 0000000..5f542ca --- /dev/null +++ b/LibExternal/Minio/Exceptions/UnexpectedShortReadException.cs @@ -0,0 +1,49 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class UnexpectedShortReadException : MinioException +{ + public UnexpectedShortReadException(string message) : base(message) + { + } + + public UnexpectedShortReadException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public UnexpectedShortReadException(string message, ResponseResult serverResponse) : base(message, serverResponse) + { + } + + public UnexpectedShortReadException() + { + } + + public UnexpectedShortReadException(string message, Exception innerException) : base(message, innerException) + { + } + + protected UnexpectedShortReadException(SerializationInfo serializationInfo, StreamingContext streamingContext) : + base(serializationInfo, streamingContext) + { + } +} diff --git a/LibExternal/Minio/Exceptions/VersionDeletedException.cs b/LibExternal/Minio/Exceptions/VersionDeletedException.cs new file mode 100644 index 0000000..c6ada9e --- /dev/null +++ b/LibExternal/Minio/Exceptions/VersionDeletedException.cs @@ -0,0 +1,61 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.Serialization; +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class VersionDeletedException : MinioException +{ + private readonly string versionId; + + public VersionDeletedException(string vid, string message) : base(message) + { + versionId = vid; + } + + public VersionDeletedException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public VersionDeletedException(string message) : base(message) + { + } + + public VersionDeletedException(string message, ResponseResult serverResponse) : base(message, serverResponse) + { + } + + public VersionDeletedException() + { + } + + public VersionDeletedException(string message, Exception innerException) : base(message, innerException) + { + } + + protected VersionDeletedException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base( + serializationInfo, streamingContext) + { + } + + public override string ToString() + { + return $"{versionId}: {base.ToString()}"; + } +} diff --git a/LibExternal/Minio/Handlers/DefaultErrorHandler.cs b/LibExternal/Minio/Handlers/DefaultErrorHandler.cs new file mode 100644 index 0000000..0ef4dc5 --- /dev/null +++ b/LibExternal/Minio/Handlers/DefaultErrorHandler.cs @@ -0,0 +1,15 @@ +using System.Net; +using Minio.DataModel.Result; + +namespace Minio.Handlers; + +public class DefaultErrorHandler : IApiResponseErrorHandler +{ + public void Handle(ResponseResult response) + { + if (response is null) throw new ArgumentNullException(nameof(response)); + + if (response.StatusCode is < HttpStatusCode.OK or >= HttpStatusCode.BadRequest) + MinioClient.ParseError(response); + } +} diff --git a/LibExternal/Minio/Handlers/DefaultRequestLogger.cs b/LibExternal/Minio/Handlers/DefaultRequestLogger.cs new file mode 100644 index 0000000..d1c0390 --- /dev/null +++ b/LibExternal/Minio/Handlers/DefaultRequestLogger.cs @@ -0,0 +1,103 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//using System.Text; +//using Microsoft.Extensions.Logging; +//using Minio.DataModel.Tracing; + +//namespace Minio.Handlers; + +//public sealed class DefaultRequestLogger : IRequestLogger +//{ +// private readonly ILogger logger; + +// public DefaultRequestLogger() +// { +// } + +// public DefaultRequestLogger(ILogger logger) +// { +// this.logger = logger; +// } + +// public void LogRequest(RequestToLog requestToLog, ResponseToLog responseToLog, double durationMs) +// { +// if (requestToLog is null) throw new ArgumentNullException(nameof(requestToLog)); + +// if (responseToLog is null) throw new ArgumentNullException(nameof(responseToLog)); + +// var sb = new StringBuilder("Request completed in "); + +// _ = sb.Append(durationMs); +// _ = sb.AppendLine(" ms"); + +// _ = sb.AppendLine(); +// _ = sb.AppendLine("- - - - - - - - - - BEGIN REQUEST - - - - - - - - - -"); +// _ = sb.AppendLine(); +// _ = sb.Append(requestToLog.Method); +// _ = sb.Append(' '); +// _ = sb.Append(requestToLog.Uri); +// _ = sb.AppendLine(" HTTP/1.1"); + +// var requestHeaders = requestToLog.Parameters; +// requestHeaders = +// requestHeaders.OrderByDescending(p => string.Equals(p.Name, "Host", StringComparison.OrdinalIgnoreCase)); + +// foreach (var item in requestHeaders) +// { +// _ = sb.Append(item.Name); +// _ = sb.Append(": "); +// _ = sb.Append(item.Value).AppendLine(); +// } + +// _ = sb.AppendLine(); +// _ = sb.AppendLine(); + +// _ = sb.AppendLine("- - - - - - - - - - END REQUEST - - - - - - - - - -"); +// _ = sb.AppendLine(); + +// _ = sb.AppendLine("- - - - - - - - - - BEGIN RESPONSE - - - - - - - - - -"); +// _ = sb.AppendLine(); + +// _ = sb.Append("HTTP/1.1 "); +// _ = sb.Append((int)responseToLog.StatusCode); +// _ = sb.Append(' '); +// _ = sb.AppendLine(responseToLog.StatusCode.ToString()); + +// var responseHeaders = responseToLog.Headers; + +// foreach (var item in responseHeaders) +// { +// _ = sb.Append(item.Key); +// _ = sb.Append(": "); +// _ = sb.AppendLine(item.Value); +// } + +// _ = sb.AppendLine(); +// _ = sb.AppendLine(); + +// _ = sb.AppendLine(responseToLog.Content); +// _ = sb.AppendLine(responseToLog.ErrorMessage); + +// _ = sb.AppendLine("- - - - - - - - - - END RESPONSE - - - - - - - - - -"); + + +// if (logger is not null) +// logger.LogInformation(sb.ToString()); +// else +// Console.WriteLine(sb.ToString()); +// } +//} diff --git a/LibExternal/Minio/Handlers/DefaultRetryPolicyHandler.cs b/LibExternal/Minio/Handlers/DefaultRetryPolicyHandler.cs new file mode 100644 index 0000000..1b8e72f --- /dev/null +++ b/LibExternal/Minio/Handlers/DefaultRetryPolicyHandler.cs @@ -0,0 +1,27 @@ +using Minio.DataModel.Result; + +namespace Minio.Handlers; + +public class DefaultRetryPolicyHandler : IRetryPolicyHandler +{ + public DefaultRetryPolicyHandler() + { + } + + public DefaultRetryPolicyHandler(Func>, Task> retryPolicyHandler) + { + RetryPolicyHandler = retryPolicyHandler; + } + + public Func>, Task> RetryPolicyHandler { get; } + + public virtual Task Handle(Func> executeRequestCallback) + { + if (executeRequestCallback is null) throw new ArgumentNullException(nameof(executeRequestCallback)); + + if (RetryPolicyHandler is not null) + return RetryPolicyHandler.Invoke(executeRequestCallback); + + return executeRequestCallback.Invoke(); + } +} diff --git a/LibExternal/Minio/Handlers/IApiResponseErrorHandler.cs b/LibExternal/Minio/Handlers/IApiResponseErrorHandler.cs new file mode 100644 index 0000000..79ea639 --- /dev/null +++ b/LibExternal/Minio/Handlers/IApiResponseErrorHandler.cs @@ -0,0 +1,8 @@ +using Minio.DataModel.Result; + +namespace Minio.Handlers; + +public interface IApiResponseErrorHandler +{ + void Handle(ResponseResult response); +} diff --git a/LibExternal/Minio/Handlers/IRequestLogger.cs b/LibExternal/Minio/Handlers/IRequestLogger.cs new file mode 100644 index 0000000..93b56ed --- /dev/null +++ b/LibExternal/Minio/Handlers/IRequestLogger.cs @@ -0,0 +1,24 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.DataModel.Tracing; + +namespace Minio.Handlers; + +public interface IRequestLogger +{ + void LogRequest(RequestToLog requestToLog, ResponseToLog responseToLog, double durationMs); +} diff --git a/LibExternal/Minio/Handlers/IRetryPolicyHandler.cs b/LibExternal/Minio/Handlers/IRetryPolicyHandler.cs new file mode 100644 index 0000000..aa3768f --- /dev/null +++ b/LibExternal/Minio/Handlers/IRetryPolicyHandler.cs @@ -0,0 +1,8 @@ +using Minio.DataModel.Result; + +namespace Minio.Handlers; + +public interface IRetryPolicyHandler +{ + Task Handle(Func> executeRequestCallback); +} diff --git a/LibExternal/Minio/Helper/AmazonAwsS3XmlReader.cs b/LibExternal/Minio/Helper/AmazonAwsS3XmlReader.cs new file mode 100644 index 0000000..7ddafe9 --- /dev/null +++ b/LibExternal/Minio/Helper/AmazonAwsS3XmlReader.cs @@ -0,0 +1,28 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017-2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Xml; + +namespace Minio.Helper; + +public class AmazonAwsS3XmlReader : XmlTextReader +{ + public AmazonAwsS3XmlReader(Stream stream) : base(stream) + { + } + + public override string NamespaceURI => "http://s3.amazonaws.com/doc/2006-03-01/"; +} diff --git a/LibExternal/Minio/Helper/BuilderUtil.cs b/LibExternal/Minio/Helper/BuilderUtil.cs new file mode 100644 index 0000000..8be1fa6 --- /dev/null +++ b/LibExternal/Minio/Helper/BuilderUtil.cs @@ -0,0 +1,136 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Globalization; +using System.Net; + +namespace Minio.Helper; + +public static class BuilderUtil +{ + public static bool IsAwsDualStackEndpoint(string endpoint) + { + if (string.IsNullOrEmpty(endpoint)) + throw new ArgumentException($"'{nameof(endpoint)}' cannot be null or empty.", nameof(endpoint)); + + return endpoint.Contains(".dualstack.", StringComparison.OrdinalIgnoreCase); + } + + public static bool IsAwsAccelerateEndpoint(string endpoint) + { + if (string.IsNullOrEmpty(endpoint)) + throw new ArgumentException($"'{nameof(endpoint)}' cannot be null or empty.", nameof(endpoint)); + + return endpoint.StartsWith("s3-accelerate.", StringComparison.OrdinalIgnoreCase); + } + + public static bool IsAwsEndpoint(string endpoint) + { + if (string.IsNullOrEmpty(endpoint)) + throw new ArgumentException($"'{nameof(endpoint)}' cannot be null or empty.", nameof(endpoint)); + + return (endpoint.StartsWith("s3.", StringComparison.OrdinalIgnoreCase) || + IsAwsAccelerateEndpoint(endpoint)) && + (endpoint.EndsWith(".amazonaws.com", StringComparison.OrdinalIgnoreCase) || + endpoint.EndsWith(".amazonaws.com.cn", StringComparison.OrdinalIgnoreCase)); + } + + public static bool IsChineseDomain(string host) + { + if (string.IsNullOrEmpty(host)) + throw new ArgumentException($"'{nameof(host)}' cannot be null or empty.", nameof(host)); + + return host.EndsWith(".cn", StringComparison.OrdinalIgnoreCase); + } + + public static string ExtractRegion(string endpoint) + { + if (string.IsNullOrEmpty(endpoint)) + throw new ArgumentException($"'{nameof(endpoint)}' cannot be null or empty.", nameof(endpoint)); + + var tokens = endpoint.Split('.'); + if (tokens.Length < 2) + return null; + var token = tokens[1]; + + // If token is "dualstack", then region might be in next token. + if (token.Equals("dualstack", StringComparison.OrdinalIgnoreCase) && tokens.Length >= 3) + token = tokens[2]; + + // If token is equal to "amazonaws", region is not passed in the endpoint. + if (token.Equals("amazonaws", StringComparison.OrdinalIgnoreCase)) + return null; + + // Return token as region. + return token; + } + + private static bool IsValidSmallInt(string val) + { + return byte.TryParse(val, out _); + } + + private static bool IsValidOctetVal(string val) + { + const byte uLimit = 255; + return byte.Parse(val, NumberStyles.Integer, CultureInfo.InvariantCulture) <= uLimit; + } + + private static bool IsValidIPv4(string ip) + { + var posColon = ip.LastIndexOf(':'); + if (posColon != -1) ip = ip[..posColon]; + var octetsStr = ip.Split('.'); + if (octetsStr.Length != 4) return false; + var isValidSmallInt = Array.TrueForAll(octetsStr, IsValidSmallInt); + if (!isValidSmallInt) return false; + return Array.TrueForAll(octetsStr, IsValidOctetVal); + } + + private static bool IsValidIP(string host) + { + return IPAddress.TryParse(host, out _); + } + + public static bool IsValidHostnameOrIPAddress(string host) + { + // Let's do IP address check first. + if (string.IsNullOrWhiteSpace(host)) return false; + // IPv4 first + if (IsValidIPv4(host)) return true; + // IPv6 or other IP address format + if (IsValidIP(host)) return true; + // Remove any port in endpoint, in such a case. + var posColon = host.LastIndexOf(':'); + if (posColon != -1) + { + try + { + var port = int.Parse(host.Substring(posColon + 1, host.Length - posColon - 1), + CultureInfo.InvariantCulture); + } + catch (FormatException) + { + return false; + } + + host = host[..posColon]; + } + + // Check host if it is a hostname. + return Uri.CheckHostName(host).ToString().Equals("dns", StringComparison.OrdinalIgnoreCase); + } +} diff --git a/LibExternal/Minio/Helper/Constants.cs b/LibExternal/Minio/Helper/Constants.cs new file mode 100644 index 0000000..ef3ca16 --- /dev/null +++ b/LibExternal/Minio/Helper/Constants.cs @@ -0,0 +1,87 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.Helper; + +internal static class Constants +{ + /// + /// Maximum number of parts + /// + public static int MaxParts = 10000; + + /// + /// Minimum part size + /// + public static long MinimumPartSize = 5 * 1024L * 1024L; + + /// + /// Minimum PUT part size + /// + public static long MinimumPUTPartSize = 16 * 1024L * 1024L; + + /// + /// Minimum COPY part size + /// + public static long MinimumCOPYPartSize = 512 * 1024L * 1024L; + + /// + /// Maximum part size + /// + public static long MaximumPartSize = 5 * 1024L * 1024L * 1024L; + + /// + /// Maximum streaming object size + /// + public static long MaximumStreamObjectSize = MaxParts * MinimumPartSize; + + /// + /// maxSinglePutObjectSize - maximum size 5GiB of object per PUT operation + /// + public static long MaxSinglePutObjectSize = 1024L * 1024L * 1024L * 5; + + /// + /// maxSingleCopyObjectSize - 5GiB + /// + public static long MaxSingleCopyObjectSize = 1024L * 1024L * 1024L * 5; + + /// + /// maxMultipartPutObjectSize - maximum size 5TiB of object for Multipart operation + /// + public static long MaxMultipartPutObjectSize = 1024L * 1024L * 1024L * 1024L * 5; + + /// + /// OptimalReadBufferSize - optimal buffer 5MiB used for reading through Read operation + /// + public static long OptimalReadBufferSize = 1024L * 1024L * 5; + + public static int DefaultExpiryTime = 7 * 24 * 3600; + + /// + /// SSEGenericHeader is the AWS SSE header used for SSE-S3 and SSE-KMS. + /// + public static string SSEGenericHeader = "X-Amz-Server-Side-Encryption"; + + /// + /// SSEKMSKeyId is the AWS SSE KMS Key-Id + /// + public static string SSEKMSKeyId = "X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id"; + + /// + /// SSEKMSContext is the AWS SSE KMS Context. + /// + public static string SSEKMSContext = "X-Amz-Server-Side-Encryption-Context"; +} diff --git a/LibExternal/Minio/Helper/OperationsHelper.cs b/LibExternal/Minio/Helper/OperationsHelper.cs new file mode 100644 index 0000000..81ac993 --- /dev/null +++ b/LibExternal/Minio/Helper/OperationsHelper.cs @@ -0,0 +1,294 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Diagnostics.CodeAnalysis; +using Minio.DataModel; +using Minio.DataModel.Args; +using Minio.DataModel.Response; +using Minio.Exceptions; +using Minio.Helper; + +namespace Minio; + +[SuppressMessage("Design", "MA0048:File name must match type name", Justification = "Split up in partial classes")] +public partial class MinioClient : IMinioClient +{ + /// + /// private helper method to remove list of objects from bucket + /// + /// GetObjectArgs Arguments Object encapsulates information like - bucket name, object name etc + /// Optional cancellation token to cancel the operation + private async Task GetObjectHelper(GetObjectArgs args, CancellationToken cancellationToken = default) + { + // StatObject is called to both verify the existence of the object and return it with GetObject. + // NOTE: This avoids writing the error body to the action stream passed (Do not remove). + + var statArgs = new StatObjectArgs() + .WithBucket(args.BucketName) + .WithObject(args.ObjectName) + .WithVersionId(args.VersionId) + .WithMatchETag(args.MatchETag) + .WithNotMatchETag(args.NotMatchETag) + .WithModifiedSince(args.ModifiedSince) + .WithUnModifiedSince(args.UnModifiedSince) + .WithServerSideEncryption(args.SSE) + .WithHeaders(args.Headers); + if (args.OffsetLengthSet) _ = statArgs.WithOffsetAndLength(args.ObjectOffset, args.ObjectLength); + var objStat = await StatObjectAsync(statArgs, cancellationToken).ConfigureAwait(false); + args?.Validate(); + if (args.FileName is not null) + await GetObjectFileAsync(args, objStat, cancellationToken).ConfigureAwait(false); + else await GetObjectStreamAsync(args, cancellationToken).ConfigureAwait(false); + return objStat; + } + + /// + /// private helper method return the specified object from the bucket + /// + /// GetObjectArgs Arguments Object encapsulates information like - bucket name, object name etc + /// ObjectStat object encapsulates information like - object name, size, etag etc + /// Optional cancellation token to cancel the operation + private Task GetObjectFileAsync(GetObjectArgs args, ObjectStat objectStat, + CancellationToken cancellationToken = default) + { + var length = objectStat.Size; + var etag = objectStat.ETag; + + var tempFileName = $"{args.FileName}.{etag}.part.minio"; + if (!string.IsNullOrEmpty(args.VersionId)) tempFileName = $"{args.FileName}.{etag}.{args.VersionId}.part.minio"; + if (File.Exists(args.FileName)) File.Delete(args.FileName); + + Utils.ValidateFile(tempFileName); + if (File.Exists(tempFileName)) File.Delete(tempFileName); + + async Task callbackAsync(Stream stream, CancellationToken cancellationToken) + { +#if NETSTANDARD + using var dest = new FileStream(tempFileName, FileMode.Create, FileAccess.Write); + await stream.CopyToAsync(dest).ConfigureAwait(false); +#else + var dest = new FileStream(tempFileName, FileMode.Create, FileAccess.Write); + await using (dest.ConfigureAwait(false)) + { + await stream.CopyToAsync(dest, cancellationToken).ConfigureAwait(false); + } +#endif + } + +#pragma warning disable IDISP001 // Dispose created + var cts = new CancellationTokenSource(); +#pragma warning restore IDISP001 // Dispose created + cts.CancelAfter(TimeSpan.FromSeconds(15)); + args.WithCallbackStream(async (stream, cancellationToken) => + { + await callbackAsync(stream, cancellationToken).ConfigureAwait(false); + Utils.MoveWithReplace(tempFileName, args.FileName); + }); + return GetObjectStreamAsync(args, cancellationToken); + } + + /// + /// private helper method. It returns the specified portion or full object from the bucket + /// + /// GetObjectArgs Arguments Object encapsulates information like - bucket name, object name etc + /// Optional cancellation token to cancel the operation + private async Task GetObjectStreamAsync(GetObjectArgs args, CancellationToken cancellationToken = default) + { + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken).ConfigureAwait(false); + } + + /// + /// private helper method to remove list of objects from bucket + /// + /// + /// RemoveObjectsArgs Arguments Object encapsulates information like - bucket name, List of objects, + /// optional list of versions (for each object) to be deleted + /// + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key provided is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When configuration XML provided is invalid + private async Task> RemoveBucketObjectsAsync(RemoveObjectsArgs args, + CancellationToken cancellationToken) + { + var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + using var response = + await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + var removeObjectsResponse = new RemoveObjectsResponse(response.StatusCode, response.Content); + return removeObjectsResponse.DeletedObjectsResult.ErrorList; + } + + /// + /// private helper method to call remove objects function + /// + /// + /// RemoveObjectsArgs Arguments Object encapsulates information like - bucket name, List of objects, + /// optional version Id list + /// + /// List of Tuples. Each tuple is Object name to List of Version IDs to be deleted + /// + /// Full List of DeleteError objects. The error list from this call will be added to the full + /// list. + /// + /// Optional cancellation token to cancel the operation + /// + private async Task> CallRemoveObjectVersions(RemoveObjectsArgs args, + IList> objVersions, List fullErrorsList, CancellationToken cancellationToken) + { + var iterArgs = new RemoveObjectsArgs() + .WithBucket(args.BucketName) + .WithObjectsVersions(objVersions); + var errorsList = await RemoveBucketObjectsAsync(iterArgs, cancellationToken).ConfigureAwait(false); + fullErrorsList.AddRange(errorsList); + return fullErrorsList; + } + + /// + /// private helper method to call function to remove objects/version items in iterations of 1000 each from bucket + /// + /// + /// RemoveObjectsArgs Arguments Object encapsulates information like - bucket name, List of objects, + /// optional list of versions (for each object) to be deleted + /// + /// List of Object names to be deleted + /// + /// Full List of DeleteError objects. The error list from this call will be added to the full + /// list. + /// + /// Optional cancellation token to cancel the operation + /// + private async Task> CallRemoveObjects(RemoveObjectsArgs args, IList objNames, + List fullErrorsList, CancellationToken cancellationToken) + { + // var requestMessageBuilder = await this.CreateRequest(args).ConfigureAwait(false); + var iterArgs = new RemoveObjectsArgs() + .WithBucket(args.BucketName) + .WithObjects(objNames); + var errorsList = await RemoveBucketObjectsAsync(iterArgs, cancellationToken).ConfigureAwait(false); + fullErrorsList.AddRange(errorsList); + return fullErrorsList; + } + + /// + /// private helper method to remove objects/version items in iterations of 1000 each from bucket + /// + /// + /// RemoveObjectsArgs Arguments Object encapsulates information like - bucket name, List of objects, + /// optional list of versions (for each object) to be deleted + /// + /// + /// Full List of DeleteError objects. The error list from this call will be added to the full + /// list. + /// + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key provided is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When configuration XML provided is invalid + private async Task> RemoveObjectVersionsHelper(RemoveObjectsArgs args, + List fullErrorsList, CancellationToken cancellationToken) + { + if (args.ObjectNamesVersions.Count <= 1000) + { + fullErrorsList.AddRange(await CallRemoveObjectVersions(args, args.ObjectNamesVersions, fullErrorsList, + cancellationToken).ConfigureAwait(false)); + return fullErrorsList; + } + + var curItemList = new List>(args.ObjectNamesVersions.GetRange(0, 1000)); + var delVersionNextIndex = curItemList.Count; + var deletedCount = 0; + while (delVersionNextIndex <= args.ObjectNamesVersions.Count) + { + var errorList = await CallRemoveObjectVersions(args, curItemList, fullErrorsList, cancellationToken) + .ConfigureAwait(false); + if (delVersionNextIndex == args.ObjectNamesVersions.Count) + break; + deletedCount += curItemList.Count; + fullErrorsList.AddRange(errorList); + curItemList.Clear(); + if (args.ObjectNamesVersions.Count - delVersionNextIndex <= 1000) + { + curItemList.AddRange(args.ObjectNamesVersions.GetRange(delVersionNextIndex, + args.ObjectNamesVersions.Count - delVersionNextIndex)); + delVersionNextIndex = args.ObjectNamesVersions.Count; + } + else + { + curItemList.AddRange(args.ObjectNamesVersions.GetRange(delVersionNextIndex, 1000)); + delVersionNextIndex += 1000; + } + } + + return fullErrorsList; + } + + /// + /// private helper method to remove objects in iterations of 1000 each from bucket + /// + /// + /// RemoveObjectsArgs Arguments Object encapsulates information like - bucket name, List of objects, + /// optional list of versions (for each object) to be deleted + /// + /// + /// Full List of DeleteError objects. The error list from this call will be added to the full + /// list. + /// + /// Optional cancellation token to cancel the operation + /// + /// When access or secret key provided is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When configuration XML provided is invalid + private async Task> RemoveObjectsHelper(RemoveObjectsArgs args, + IList fullErrorsList, + CancellationToken cancellationToken) + { + var iterObjects = new List(1000); + var i = 0; + foreach (var objName in args.ObjectNames) + { + Utils.ValidateObjectName(objName); + iterObjects.Insert(i, objName); + if (++i == 1000) + { + fullErrorsList = await CallRemoveObjects(args, iterObjects, fullErrorsList.ToList(), cancellationToken) + .ConfigureAwait(false); + iterObjects.Clear(); + i = 0; + } + } + + if (iterObjects.Count > 0) + fullErrorsList = await CallRemoveObjects(args, iterObjects, fullErrorsList.ToList(), cancellationToken) + .ConfigureAwait(false); + return fullErrorsList; + } +} diff --git a/LibExternal/Minio/Helper/OperationsUtil.cs b/LibExternal/Minio/Helper/OperationsUtil.cs new file mode 100644 index 0000000..36e9754 --- /dev/null +++ b/LibExternal/Minio/Helper/OperationsUtil.cs @@ -0,0 +1,52 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio.Helper; + +public static class OperationsUtil +{ + private static readonly List SupportedHeaders = new() + { + "cache-control", + "content-encoding", + "content-type", + "x-amz-acl", + "content-disposition", + "x-minio-extract" + }; + + private static readonly List SSEHeaders = new() + { + "X-Amz-Server-Side-Encryption-Customer-Algorithm", + "X-Amz-Server-Side-Encryption-Customer-Key", + "X-Amz-Server-Side-Encryption-Customer-Key-Md5", + Constants.SSEGenericHeader, + Constants.SSEKMSKeyId, + Constants.SSEKMSContext + }; + + internal static bool IsSupportedHeader(string hdr, IEqualityComparer comparer = null) + { + comparer ??= StringComparer.OrdinalIgnoreCase; + return SupportedHeaders.Contains(hdr, comparer); + } + + internal static bool IsSSEHeader(string hdr, IEqualityComparer comparer = null) + { + comparer ??= StringComparer.OrdinalIgnoreCase; + return SSEHeaders.Contains(hdr, comparer); + } +} diff --git a/LibExternal/Minio/Helper/RegionHelper.cs b/LibExternal/Minio/Helper/RegionHelper.cs new file mode 100644 index 0000000..8df2458 --- /dev/null +++ b/LibExternal/Minio/Helper/RegionHelper.cs @@ -0,0 +1,55 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Collections.Concurrent; +using System.Text.RegularExpressions; + +namespace Minio.Helper; + +public static class RegionHelper + +{ + private static readonly Regex endpointRegex = new(@"s3[.\-](?.*?)\.amazonaws\.com$", + RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.RightToLeft, + TimeSpan.FromHours(1)); + + private static readonly ConcurrentDictionary cache = new(StringComparer.Ordinal); + + /// + /// Get corresponding region for input host. + /// + /// S3 API endpoint + /// Region corresponding to the endpoint. Default is 'us-east-1' + public static string GetRegionFromEndpoint(string endpoint) + { + if (!string.IsNullOrEmpty(endpoint)) + return cache.GetOrAdd(endpoint, GetRegionFromEndpointImpl); + return string.Empty; + } + + private static string GetRegionFromEndpointImpl(string endpoint) + { + if (!string.IsNullOrEmpty(endpoint)) + { + var match = endpointRegex.Match(endpoint); + + if (match.Success) + return match.Groups[1].Value; + } + + return string.Empty; + } +} diff --git a/LibExternal/Minio/Helper/RequestUtil.cs b/LibExternal/Minio/Helper/RequestUtil.cs new file mode 100644 index 0000000..027f8d8 --- /dev/null +++ b/LibExternal/Minio/Helper/RequestUtil.cs @@ -0,0 +1,130 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Globalization; +using System.Text.RegularExpressions; +using System.Web; +using Minio.Exceptions; + +namespace Minio.Helper; + +internal static class RequestUtil +{ + internal static Uri GetEndpointURL(string endPoint, bool secure) + { + if (endPoint.Contains(':', StringComparison.Ordinal)) + { + var parts = endPoint.Split(':'); + var host = parts[0]; + //var port = parts[1]; + if (!S3utils.IsValidIP(host) && !IsValidEndpoint(host)) + throw new InvalidEndpointException("Endpoint: " + endPoint + + " does not follow ip address or domain name standards."); + } + else + { + if (!S3utils.IsValidIP(endPoint) && !IsValidEndpoint(endPoint)) + throw new InvalidEndpointException("Endpoint: " + endPoint + + " does not follow ip address or domain name standards."); + } + + var uri = TryCreateUri(endPoint, secure); + ValidateEndpoint(uri, endPoint); + return uri; + } + + internal static Uri MakeTargetURL(string endPoint, bool secure, string bucketName = null, string region = null, + bool usePathStyle = true) + { + // For Amazon S3 endpoint, try to fetch location based endpoint. + var host = endPoint; + if (S3utils.IsAmazonEndPoint(endPoint)) + // Fetch new host based on the bucket location. + host = AWSS3Endpoints.Endpoint(region); + + if (!usePathStyle) + { + var suffix = bucketName is not null ? bucketName + "/" : ""; + host = host + "/" + suffix; + } + + var scheme = secure ? "https" : "http"; + var endpointURL = string.Format(CultureInfo.InvariantCulture, "{0}://{1}", scheme, host); + return new Uri(endpointURL, UriKind.Absolute); + } + + internal static Uri TryCreateUri(string endpoint, bool secure) + { + var scheme = secure ? HttpUtility.UrlEncode("https") : HttpUtility.UrlEncode("http"); + + // This is the actual url pointed to for all HTTP requests + var endpointURL = string.Format(CultureInfo.InvariantCulture, "{0}://{1}", scheme, endpoint); + Uri uri; + try + { + uri = new Uri(endpointURL); + } + catch (UriFormatException e) + { + throw new InvalidEndpointException(e.Message); + } + + return uri; + } + + /// + /// Validates URI to check if it is well formed. Otherwise cry foul. + /// + internal static void ValidateEndpoint(Uri uri, string endpoint) + { + if (string.IsNullOrEmpty(uri.OriginalString)) throw new InvalidEndpointException("Endpoint cannot be empty."); + + if (!IsValidEndpoint(uri.Host)) throw new InvalidEndpointException(endpoint, "Invalid endpoint."); + if (!uri.AbsolutePath.Equals("/", StringComparison.OrdinalIgnoreCase)) + throw new InvalidEndpointException(endpoint, "No path allowed in endpoint."); + + if (!string.IsNullOrEmpty(uri.Query)) + throw new InvalidEndpointException(endpoint, "No query parameter allowed in endpoint."); + if (!uri.Scheme.ToUpperInvariant().Equals("https", StringComparison.OrdinalIgnoreCase) && + !uri.Scheme.ToUpperInvariant().Equals("http", StringComparison.OrdinalIgnoreCase)) + throw new InvalidEndpointException(endpoint, "Invalid scheme detected in endpoint."); + } + + /// + /// Validate Url endpoint + /// + /// + /// true/false + internal static bool IsValidEndpoint(string endpoint) + { + // endpoint may be a hostname + // refer https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names + // why checks are as shown below. + if (endpoint.Length is < 1 or > 253) return false; + + foreach (var label in endpoint.Split('.')) + { + if (label.Length is < 1 or > 63) return false; + + var validLabel = new Regex("^[a-zA-Z0-9]([A-Za-z0-9-_]*[a-zA-Z0-9])?$", RegexOptions.ExplicitCapture, + TimeSpan.FromSeconds(3)); + + if (!validLabel.IsMatch(label)) return false; + } + + return true; + } +} diff --git a/LibExternal/Minio/Helper/S3utils.cs b/LibExternal/Minio/Helper/S3utils.cs new file mode 100644 index 0000000..8ed9bed --- /dev/null +++ b/LibExternal/Minio/Helper/S3utils.cs @@ -0,0 +1,96 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Text.RegularExpressions; + +namespace Minio.Helper; + +internal static class S3utils +{ + internal static readonly Regex TrimWhitespaceRegex = new("\\s+", RegexOptions.None, TimeSpan.FromHours(1)); + + internal static bool IsAmazonEndPoint(string endpoint) + { + if (IsAmazonChinaEndPoint(endpoint)) return true; + var rgx = new Regex("^s3[.-]?(.*?)\\.amazonaws\\.com$", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture, + TimeSpan.FromSeconds(3)); + var matches = rgx.Matches(endpoint); + return matches.Count > 0; + } + + // IsAmazonChinaEndpoint - Match if it is exactly Amazon S3 China endpoint. + // Customers who wish to use the new Beijing Region are required + // to sign up for a separate set of account credentials unique to + // the China (Beijing) Region. Customers with existing AWS credentials + // will not be able to access resources in the new Region, and vice versa. + // For more info https://aws.amazon.com/about-aws/whats-new/2013/12/18/announcing-the-aws-china-beijing-region/ + internal static bool IsAmazonChinaEndPoint(string endpoint) + { + return string.Equals(endpoint, "s3.cn-north-1.amazonaws.com.cn", StringComparison.OrdinalIgnoreCase); + } + + // IsVirtualHostSupported - verifies if bucketName can be part of + // virtual host. Currently only Amazon S3 and Google Cloud Storage + // would support this. + internal static bool IsVirtualHostSupported(Uri endpointURL, string bucketName) + { + if (endpointURL is null) return false; + // bucketName can be valid but '.' in the hostname will fail SSL + // certificate validation. So do not use host-style for such buckets. + if (string.Equals(endpointURL.Scheme, "https", StringComparison.OrdinalIgnoreCase) && + bucketName.Contains('.', StringComparison.Ordinal)) return false; + // Return true for all other cases + return IsAmazonEndPoint(endpointURL.Host); + } + + internal static string GetPath(string p1, string p2) + { + try + { + var combination = Path.Combine(p1, p2); + // combination = Uri.EscapeUriString(combination); + return Utils.EncodePath(combination); + } + catch (Exception ex) + { + throw new DirectoryNotFoundException(ex.Message); + } + } + + /// + /// IsValidIP parses input string for ip address validity. + /// + /// + /// + internal static bool IsValidIP(string ip) + { + if (string.IsNullOrEmpty(ip)) return false; + + var splitValues = ip.Split('.'); + if (splitValues.Length != 4) return false; + + return splitValues.All(r => byte.TryParse(r, out var _)); + } + + // TrimAll trims leading and trailing spaces and replace sequential spaces with one space, following Trimall() + // in http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html + internal static string TrimAll(string s) + { + if (string.IsNullOrEmpty(s)) + return s; + return TrimWhitespaceRegex.Replace(s, " ").Trim(); + } +} diff --git a/LibExternal/Minio/Helper/Utils.cs b/LibExternal/Minio/Helper/Utils.cs new file mode 100644 index 0000000..6f234ea --- /dev/null +++ b/LibExternal/Minio/Helper/Utils.cs @@ -0,0 +1,1087 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017-2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; +using System.Xml; +using System.Xml.Linq; +using System.Xml.Serialization; +using Minio.DataModel; +using Minio.Exceptions; +#if !NET6_0_OR_GREATER +using System.Collections.Concurrent; +#endif + +namespace Minio.Helper; + +public static class Utils +{ + // We support '.' with bucket names but we fallback to using path + // style requests instead for such buckets. + private static readonly Regex validBucketName = + new("^[a-z0-9][a-z0-9\\.\\-]{1,61}[a-z0-9]$", RegexOptions.None, TimeSpan.FromHours(1)); + + // Invalid bucket name with double dot. + private static readonly Regex invalidDotBucketName = new("`/./.", RegexOptions.None, TimeSpan.FromHours(1)); + + private static readonly Lazy> contentTypeMap = new(AddContentTypeMappings); + + /// + /// IsValidBucketName - verify bucket name in accordance with + /// http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingBucket.html + /// + /// Bucket to test existence of + internal static void ValidateBucketName(string bucketName) + { + if (string.IsNullOrEmpty(bucketName)) + throw new InvalidBucketNameException(bucketName, "Bucket name cannot be empty."); + if (bucketName.Length < 3) + throw new InvalidBucketNameException(bucketName, "Bucket name cannot be smaller than 3 characters."); + if (bucketName.Length > 63) + throw new InvalidBucketNameException(bucketName, "Bucket name cannot be greater than 63 characters."); + if (bucketName[0] == '.' || bucketName[^1] == '.') + throw new InvalidBucketNameException(bucketName, "Bucket name cannot start or end with a '.' dot."); + if (bucketName.Any(char.IsUpper)) + throw new InvalidBucketNameException(bucketName, "Bucket name cannot have upper case characters"); + if (invalidDotBucketName.IsMatch(bucketName)) + throw new InvalidBucketNameException(bucketName, "Bucket name cannot have successive periods."); + if (!validBucketName.IsMatch(bucketName)) + throw new InvalidBucketNameException(bucketName, "Bucket name contains invalid characters."); + } + + // IsValidObjectName - verify object name in accordance with + // http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html + internal static void ValidateObjectName(string objectName) + { + if (string.IsNullOrEmpty(objectName) || string.IsNullOrEmpty(objectName.Trim())) + throw new InvalidObjectNameException(objectName, "Object name cannot be empty."); + + // c# strings are in utf16 format. they are already in unicode format when they arrive here. + if (objectName.Length > 512) + throw new InvalidObjectNameException(objectName, "Object name cannot be greater than 1024 characters."); + } + + internal static void ValidateObjectPrefix(string objectPrefix) + { + if (objectPrefix.Length > 512) + throw new InvalidObjectPrefixException(objectPrefix, + "Object prefix cannot be greater than 1024 characters."); + } + + // Return url encoded string where reserved characters have been percent-encoded + internal static string UrlEncode(string input) + { + // The following characters are not allowed on the server side + // '-', '_', '.', '/', '*' + return Uri.EscapeDataString(input).Replace("\\!", "%21", StringComparison.Ordinal) + .Replace("\\\"", "%22", StringComparison.Ordinal) + .Replace("\\#", "%23", StringComparison.Ordinal) + .Replace("\\$", "%24", StringComparison.Ordinal) + .Replace("\\%", "%25", StringComparison.Ordinal) + .Replace("\\&", "%26", StringComparison.Ordinal) + .Replace("\\'", "%27", StringComparison.Ordinal) + .Replace("\\(", "%28", StringComparison.Ordinal) + .Replace("\\)", "%29", StringComparison.Ordinal) + .Replace("\\+", "%2B", StringComparison.Ordinal) + .Replace("\\,", "%2C", StringComparison.Ordinal) + .Replace("\\:", "%3A", StringComparison.Ordinal) + .Replace("\\;", "%3B", StringComparison.Ordinal) + .Replace("\\<", "%3C", StringComparison.Ordinal) + .Replace("\\=", "%3D", StringComparison.Ordinal) + .Replace("\\>", "%3E", StringComparison.Ordinal) + .Replace("\\?", "%3F", StringComparison.Ordinal) + .Replace("\\@", "%40", StringComparison.Ordinal) + .Replace("\\[", "%5B", StringComparison.Ordinal) + .Replace("\\\\", "%5C", StringComparison.Ordinal) + .Replace("\\]", "%5D", StringComparison.Ordinal) + .Replace("\\^", "%5E", StringComparison.Ordinal) + .Replace("\\'", "%60", StringComparison.Ordinal) + .Replace("\\{", "%7B", StringComparison.Ordinal) + .Replace("\\|", "%7C", StringComparison.Ordinal) + .Replace("\\}", "%7D", StringComparison.Ordinal) + .Replace("\\~", "%7E", StringComparison.Ordinal); + } + + // Return encoded path where extra "/" are trimmed off. + internal static string EncodePath(string path) + { + var encodedPathBuf = new StringBuilder(); + foreach (var pathSegment in path.Split('/')) + if (pathSegment.Length != 0) + { + if (encodedPathBuf.Length > 0) _ = encodedPathBuf.Append('/'); + _ = encodedPathBuf.Append(UrlEncode(pathSegment)); + } + + if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase)) _ = encodedPathBuf.Insert(0, '/'); + if (path.EndsWith("/", StringComparison.OrdinalIgnoreCase)) _ = encodedPathBuf.Append('/'); + return encodedPathBuf.ToString(); + } + + internal static bool IsAnonymousClient(string accessKey, string secretKey) + { + return string.IsNullOrEmpty(secretKey) && string.IsNullOrEmpty(accessKey); + } + + internal static void ValidateFile(string filePath) + { + if (string.IsNullOrEmpty(filePath)) + throw new ArgumentException("empty file name is not allowed", nameof(filePath)); + + var fileName = Path.GetFileName(filePath); + var fileExists = File.Exists(filePath); + if (fileExists) + { + var attr = File.GetAttributes(filePath); + if (attr.HasFlag(FileAttributes.Directory)) + throw new ArgumentException($"'{fileName}': not a regular file", nameof(filePath)); + } + } + + internal static string GetContentType(string fileName) + { + string extension = null; + try + { + extension = Path.GetExtension(fileName); + } + catch + { + } + + if (string.IsNullOrEmpty(extension)) return "application/octet-stream"; + + return contentTypeMap.Value.TryGetValue(extension, out var contentType) + ? contentType + : "application/octet-stream"; + } + + public static void MoveWithReplace(string sourceFileName, string destFileName) + { + try + { + // first, delete target file if exists, as File.Move() does not support overwrite + if (File.Exists(destFileName)) File.Delete(destFileName); + + File.Move(sourceFileName, destFileName); + } + catch + { + } + } + + internal static bool IsSupersetOf(IList l1, IList l2) + { + if (l2 is null) return true; + + if (l1 is null) return false; + + return !l2.Except(l1, StringComparer.Ordinal).Any(); + } + + public static async Task ForEachAsync(this IEnumerable source, bool runInParallel = false, + int maxNoOfParallelProcesses = 4) where TSource : Task + { + if (source is null) throw new ArgumentNullException(nameof(source)); + + try + { + if (runInParallel) + { +#if NET6_0_OR_GREATER + ParallelOptions parallelOptions = new() + { + MaxDegreeOfParallelism + = maxNoOfParallelProcesses + }; + await Parallel.ForEachAsync(source, parallelOptions, + async (task, cancellationToken) => await task.ConfigureAwait(false)).ConfigureAwait(false); +#else + await Task.WhenAll(Partitioner.Create(source).GetPartitions(maxNoOfParallelProcesses) + .Select(partition => Task.Run(async delegate + { +#pragma warning disable IDISP007 // Don't dispose injected + using (partition) + { + while (partition.MoveNext()) + await partition.Current.ConfigureAwait(false); + } +#pragma warning restore IDISP007 // Don't dispose injected + } + ))).ConfigureAwait(false); +#endif + } + else + { + foreach (var task in source) await task.ConfigureAwait(false); + } + } + catch (AggregateException ae) + { + foreach (var ex in ae.Flatten().InnerExceptions) + // Handle or log the individual exception 'ex' + Console.WriteLine($"Exception occurred: {ex.Message}"); + } + } + + public static bool CaseInsensitiveContains(string text, string value, + StringComparison stringComparison = StringComparison.CurrentCultureIgnoreCase) + { + if (string.IsNullOrEmpty(text)) + throw new ArgumentException($"'{nameof(text)}' cannot be null or empty.", nameof(text)); + + return text.Contains(value, stringComparison); + } + + /// + /// Calculate part size and number of parts required. + /// + /// + /// If true, use COPY part size, else use PUT part size + /// + public static MultiPartInfo CalculateMultiPartSize(long size, bool copy = false) + { + if (size == -1) size = Constants.MaximumStreamObjectSize; + + if (size > Constants.MaxMultipartPutObjectSize) + throw new EntityTooLargeException( + $"Your proposed upload size {size} exceeds the maximum allowed object size {Constants.MaxMultipartPutObjectSize}"); + + var partSize = (double)Math.Ceiling((decimal)size / Constants.MaxParts); + var minPartSize = copy ? Constants.MinimumCOPYPartSize : Constants.MinimumPUTPartSize; + partSize = (double)Math.Ceiling((decimal)partSize / minPartSize) * minPartSize; + var partCount = Math.Ceiling(size / partSize); + var lastPartSize = size - ((partCount - 1) * partSize); + + return new MultiPartInfo { PartSize = partSize, PartCount = partCount, LastPartSize = lastPartSize }; + } + + /// + /// Check if input expires value is valid. + /// + /// time to expiry in seconds + /// bool + public static bool IsValidExpiry(int expiryInt) + { + return expiryInt > 0 && expiryInt <= Constants.DefaultExpiryTime; + } + + internal static string GetMD5SumStr(ReadOnlySpan key) + { +#if NETSTANDARD +#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms + using var md5 + = MD5.Create(); +#pragma warning restore CA5351 // Do Not Use Broken Cryptographic Algorithms + var hashedBytes + = md5.ComputeHash(key.ToArray()); +#else + ReadOnlySpan hashedBytes = MD5.HashData(key); +#endif + return Convert.ToBase64String(hashedBytes); + } + + [SuppressMessage("Design", "MA0051:Method is too long", Justification = "One time list of type mappings")] + private static Dictionary AddContentTypeMappings() + { + return new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { ".323", "text/h323" }, + { ".3g2", "video/3gpp2" }, + { ".3gp", "video/3gpp" }, + { ".3gp2", "video/3gpp2" }, + { ".3gpp", "video/3gpp" }, + { ".7z", "application/x-7z-compressed" }, + { ".aa", "audio/audible" }, + { ".AAC", "audio/aac" }, + { ".aax", "audio/vnd.audible.aax" }, + { ".ac3", "audio/ac3" }, + { ".accda", "application/msaccess.addin" }, + { ".accdb", "application/msaccess" }, + { ".accdc", "application/msaccess.cab" }, + { ".accde", "application/msaccess" }, + { ".accdr", "application/msaccess.runtime" }, + { ".accdt", "application/msaccess" }, + { ".accdw", "application/msaccess.webapplication" }, + { ".accft", "application/msaccess.ftemplate" }, + { ".acx", "application/internet-property-stream" }, + { ".AddIn", "text/xml" }, + { ".ade", "application/msaccess" }, + { ".adobebridge", "application/x-bridge-url" }, + { ".adp", "application/msaccess" }, + { ".ADT", "audio/vnd.dlna.adts" }, + { ".ADTS", "audio/aac" }, + { ".ai", "application/postscript" }, + { ".aif", "audio/aiff" }, + { ".aifc", "audio/aiff" }, + { ".aiff", "audio/aiff" }, + { ".air", "application/vnd.adobe.air-application-installer-package+zip" }, + { ".amc", "application/mpeg" }, + { ".anx", "application/annodex" }, + { ".apk", "application/vnd.android.package-archive" }, + { ".application", "application/x-ms-application" }, + { ".art", "image/x-jg" }, + { ".asa", "application/xml" }, + { ".asax", "application/xml" }, + { ".ascx", "application/xml" }, + { ".asf", "video/x-ms-asf" }, + { ".ashx", "application/xml" }, + { ".asm", "text/plain" }, + { ".asmx", "application/xml" }, + { ".aspx", "application/xml" }, + { ".asr", "video/x-ms-asf" }, + { ".asx", "video/x-ms-asf" }, + { ".atom", "application/atom+xml" }, + { ".au", "audio/basic" }, + { ".avi", "video/x-msvideo" }, + { ".axa", "audio/annodex" }, + { ".axs", "application/olescript" }, + { ".axv", "video/annodex" }, + { ".bas", "text/plain" }, + { ".bcpio", "application/x-bcpio" }, + { ".bmp", "image/bmp" }, + { ".c", "text/plain" }, + { ".caf", "audio/x-caf" }, + { ".calx", "application/vnd.ms-office.calx" }, + { ".cat", "application/vnd.ms-pki.seccat" }, + { ".cc", "text/plain" }, + { ".cd", "text/plain" }, + { ".cdda", "audio/aiff" }, + { ".cdf", "application/x-cdf" }, + { ".cer", "application/x-x509-ca-cert" }, + { ".cfg", "text/plain" }, + { ".class", "application/x-java-applet" }, + { ".clp", "application/x-msclip" }, + { ".cmd", "text/plain" }, + { ".cmx", "image/x-cmx" }, + { ".cnf", "text/plain" }, + { ".cod", "image/cis-cod" }, + { ".config", "application/xml" }, + { ".contact", "text/x-ms-contact" }, + { ".coverage", "application/xml" }, + { ".cpio", "application/x-cpio" }, + { ".cpp", "text/plain" }, + { ".crd", "application/x-mscardfile" }, + { ".crl", "application/pkix-crl" }, + { ".crt", "application/x-x509-ca-cert" }, + { ".cs", "text/plain" }, + { ".csdproj", "text/plain" }, + { ".csh", "application/x-csh" }, + { ".csproj", "text/plain" }, + { ".css", "text/css" }, + { ".csv", "text/csv" }, + { ".cxx", "text/plain" }, + { ".datasource", "application/xml" }, + { ".dbproj", "text/plain" }, + { ".dcr", "application/x-director" }, + { ".def", "text/plain" }, + { ".der", "application/x-x509-ca-cert" }, + { ".dgml", "application/xml" }, + { ".dib", "image/bmp" }, + { ".dif", "video/x-dv" }, + { ".dir", "application/x-director" }, + { ".disco", "text/xml" }, + { ".divx", "video/divx" }, + { ".dll", "application/x-msdownload" }, + { ".dll.config", "text/xml" }, + { ".dlm", "text/dlm" }, + { ".doc", "application/msword" }, + { ".docm", "application/vnd.ms-word.document.macroEnabled.12" }, + { ".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document" }, + { ".dot", "application/msword" }, + { ".dotm", "application/vnd.ms-word.template.macroEnabled.12" }, + { ".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template" }, + { ".dsw", "text/plain" }, + { ".dtd", "text/xml" }, + { ".dtsConfig", "text/xml" }, + { ".dv", "video/x-dv" }, + { ".dvi", "application/x-dvi" }, + { ".dwf", "drawing/x-dwf" }, + { ".dwg", "application/acad" }, + { ".dxf", "application/x-dxf" }, + { ".dxr", "application/x-director" }, + { ".eml", "message/rfc822" }, + { ".eot", "application/vnd.ms-fontobject" }, + { ".eps", "application/postscript" }, + { ".etl", "application/etl" }, + { ".etx", "text/x-setext" }, + { ".evy", "application/envoy" }, + { ".exe.config", "text/xml" }, + { ".fdf", "application/vnd.fdf" }, + { ".fif", "application/fractals" }, + { ".filters", "application/xml" }, + { ".flac", "audio/flac" }, + { ".flr", "x-world/x-vrml" }, + { ".flv", "video/x-flv" }, + { ".fsscript", "application/fsharp-script" }, + { ".fsx", "application/fsharp-script" }, + { ".generictest", "application/xml" }, + { ".gif", "image/gif" }, + { ".gpx", "application/gpx+xml" }, + { ".group", "text/x-ms-group" }, + { ".gsm", "audio/x-gsm" }, + { ".gtar", "application/x-gtar" }, + { ".gz", "application/x-gzip" }, + { ".h", "text/plain" }, + { ".hdf", "application/x-hdf" }, + { ".hdml", "text/x-hdml" }, + { ".hhc", "application/x-oleobject" }, + { ".hlp", "application/winhlp" }, + { ".hpp", "text/plain" }, + { ".hqx", "application/mac-binhex40" }, + { ".hta", "application/hta" }, + { ".htc", "text/x-component" }, + { ".htm", "text/html" }, + { ".html", "text/html" }, + { ".htt", "text/webviewhtml" }, + { ".hxa", "application/xml" }, + { ".hxc", "application/xml" }, + { ".hxe", "application/xml" }, + { ".hxf", "application/xml" }, + { ".hxk", "application/xml" }, + { ".hxt", "text/html" }, + { ".hxv", "application/xml" }, + { ".hxx", "text/plain" }, + { ".i", "text/plain" }, + { ".ico", "image/x-icon" }, + { ".idl", "text/plain" }, + { ".ief", "image/ief" }, + { ".iii", "application/x-iphone" }, + { ".inc", "text/plain" }, + { ".ini", "text/plain" }, + { ".inl", "text/plain" }, + { ".ins", "application/x-internet-signup" }, + { ".ipa", "application/x-itunes-ipa" }, + { ".ipg", "application/x-itunes-ipg" }, + { ".ipproj", "text/plain" }, + { ".ipsw", "application/x-itunes-ipsw" }, + { ".iqy", "text/x-ms-iqy" }, + { ".isp", "application/x-internet-signup" }, + { ".ite", "application/x-itunes-ite" }, + { ".itlp", "application/x-itunes-itlp" }, + { ".itms", "application/x-itunes-itms" }, + { ".itpc", "application/x-itunes-itpc" }, + { ".IVF", "video/x-ivf" }, + { ".jar", "application/java-archive" }, + { ".jck", "application/liquidmotion" }, + { ".jcz", "application/liquidmotion" }, + { ".jfif", "image/pjpeg" }, + { ".jnlp", "application/x-java-jnlp-file" }, + { ".jpe", "image/jpeg" }, + { ".jpeg", "image/jpeg" }, + { ".jpg", "image/jpeg" }, + { ".js", "application/javascript" }, + { ".json", "application/json" }, + { ".jsx", "text/jscript" }, + { ".jsxbin", "text/plain" }, + { ".latex", "application/x-latex" }, + { ".library-ms", "application/windows-library+xml" }, + { ".lit", "application/x-ms-reader" }, + { ".loadtest", "application/xml" }, + { ".lsf", "video/x-la-asf" }, + { ".lst", "text/plain" }, + { ".lsx", "video/x-la-asf" }, + { ".m13", "application/x-msmediaview" }, + { ".m14", "application/x-msmediaview" }, + { ".m1v", "video/mpeg" }, + { ".m2t", "video/vnd.dlna.mpeg-tts" }, + { ".m2ts", "video/vnd.dlna.mpeg-tts" }, + { ".m2v", "video/mpeg" }, + { ".m3u", "audio/x-mpegurl" }, + { ".m3u8", "audio/x-mpegurl" }, + { ".m4a", "audio/m4a" }, + { ".m4b", "audio/m4b" }, + { ".m4p", "audio/m4p" }, + { ".m4r", "audio/x-m4r" }, + { ".m4v", "video/x-m4v" }, + { ".mac", "image/x-macpaint" }, + { ".mak", "text/plain" }, + { ".man", "application/x-troff-man" }, + { ".manifest", "application/x-ms-manifest" }, + { ".map", "text/plain" }, + { ".master", "application/xml" }, + { ".mbox", "application/mbox" }, + { ".mda", "application/msaccess" }, + { ".mdb", "application/x-msaccess" }, + { ".mde", "application/msaccess" }, + { ".me", "application/x-troff-me" }, + { ".mfp", "application/x-shockwave-flash" }, + { ".mht", "message/rfc822" }, + { ".mhtml", "message/rfc822" }, + { ".mid", "audio/mid" }, + { ".midi", "audio/mid" }, + { ".mk", "text/plain" }, + { ".mmf", "application/x-smaf" }, + { ".mno", "text/xml" }, + { ".mny", "application/x-msmoney" }, + { ".mod", "video/mpeg" }, + { ".mov", "video/quicktime" }, + { ".movie", "video/x-sgi-movie" }, + { ".mp2", "video/mpeg" }, + { ".mp2v", "video/mpeg" }, + { ".mp3", "audio/mpeg" }, + { ".mp4", "video/mp4" }, + { ".mp4v", "video/mp4" }, + { ".mpa", "video/mpeg" }, + { ".mpe", "video/mpeg" }, + { ".mpeg", "video/mpeg" }, + { ".mpf", "application/vnd.ms-mediapackage" }, + { ".mpg", "video/mpeg" }, + { ".mpp", "application/vnd.ms-project" }, + { ".mpv2", "video/mpeg" }, + { ".mqv", "video/quicktime" }, + { ".ms", "application/x-troff-ms" }, + { ".msg", "application/vnd.ms-outlook" }, + { ".mts", "video/vnd.dlna.mpeg-tts" }, + { ".mtx", "application/xml" }, + { ".mvb", "application/x-msmediaview" }, + { ".mvc", "application/x-miva-compiled" }, + { ".mxp", "application/x-mmxp" }, + { ".nc", "application/x-netcdf" }, + { ".nsc", "video/x-ms-asf" }, + { ".nws", "message/rfc822" }, + { ".oda", "application/oda" }, + { ".odb", "application/vnd.oasis.opendocument.database" }, + { ".odc", "application/vnd.oasis.opendocument.chart" }, + { ".odf", "application/vnd.oasis.opendocument.formula" }, + { ".odg", "application/vnd.oasis.opendocument.graphics" }, + { ".odh", "text/plain" }, + { ".odi", "application/vnd.oasis.opendocument.image" }, + { ".odl", "text/plain" }, + { ".odm", "application/vnd.oasis.opendocument.text-master" }, + { ".odp", "application/vnd.oasis.opendocument.presentation" }, + { ".ods", "application/vnd.oasis.opendocument.spreadsheet" }, + { ".odt", "application/vnd.oasis.opendocument.text" }, + { ".oga", "audio/ogg" }, + { ".ogg", "audio/ogg" }, + { ".ogv", "video/ogg" }, + { ".ogx", "application/ogg" }, + { ".one", "application/onenote" }, + { ".onea", "application/onenote" }, + { ".onepkg", "application/onenote" }, + { ".onetmp", "application/onenote" }, + { ".onetoc", "application/onenote" }, + { ".onetoc2", "application/onenote" }, + { ".opus", "audio/ogg" }, + { ".orderedtest", "application/xml" }, + { ".osdx", "application/opensearchdescription+xml" }, + { ".otf", "application/font-sfnt" }, + { ".otg", "application/vnd.oasis.opendocument.graphics-template" }, + { ".oth", "application/vnd.oasis.opendocument.text-web" }, + { ".otp", "application/vnd.oasis.opendocument.presentation-template" }, + { ".ots", "application/vnd.oasis.opendocument.spreadsheet-template" }, + { ".ott", "application/vnd.oasis.opendocument.text-template" }, + { ".oxt", "application/vnd.openofficeorg.extension" }, + { ".p10", "application/pkcs10" }, + { ".p12", "application/x-pkcs12" }, + { ".p7b", "application/x-pkcs7-certificates" }, + { ".p7c", "application/pkcs7-mime" }, + { ".p7m", "application/pkcs7-mime" }, + { ".p7r", "application/x-pkcs7-certreqresp" }, + { ".p7s", "application/pkcs7-signature" }, + { ".pbm", "image/x-portable-bitmap" }, + { ".pcast", "application/x-podcast" }, + { ".pct", "image/pict" }, + { ".pdf", "application/pdf" }, + { ".pfx", "application/x-pkcs12" }, + { ".pgm", "image/x-portable-graymap" }, + { ".pic", "image/pict" }, + { ".pict", "image/pict" }, + { ".pkgdef", "text/plain" }, + { ".pkgundef", "text/plain" }, + { ".pko", "application/vnd.ms-pki.pko" }, + { ".pls", "audio/scpls" }, + { ".pma", "application/x-perfmon" }, + { ".pmc", "application/x-perfmon" }, + { ".pml", "application/x-perfmon" }, + { ".pmr", "application/x-perfmon" }, + { ".pmw", "application/x-perfmon" }, + { ".png", "image/png" }, + { ".pnm", "image/x-portable-anymap" }, + { ".pnt", "image/x-macpaint" }, + { ".pntg", "image/x-macpaint" }, + { ".pnz", "image/png" }, + { ".pot", "application/vnd.ms-powerpoint" }, + { ".potm", "application/vnd.ms-powerpoint.template.macroEnabled.12" }, + { ".potx", "application/vnd.openxmlformats-officedocument.presentationml.template" }, + { ".ppa", "application/vnd.ms-powerpoint" }, + { ".ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12" }, + { ".ppm", "image/x-portable-pixmap" }, + { ".pps", "application/vnd.ms-powerpoint" }, + { ".ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12" }, + { ".ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow" }, + { ".ppt", "application/vnd.ms-powerpoint" }, + { ".pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12" }, + { ".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation" }, + { ".prf", "application/pics-rules" }, + { ".ps", "application/postscript" }, + { ".psc1", "application/PowerShell" }, + { ".psess", "application/xml" }, + { ".pst", "application/vnd.ms-outlook" }, + { ".pub", "application/x-mspublisher" }, + { ".pwz", "application/vnd.ms-powerpoint" }, + { ".qht", "text/x-html-insertion" }, + { ".qhtm", "text/x-html-insertion" }, + { ".qt", "video/quicktime" }, + { ".qti", "image/x-quicktime" }, + { ".qtif", "image/x-quicktime" }, + { ".qtl", "application/x-quicktimeplayer" }, + { ".ra", "audio/x-pn-realaudio" }, + { ".ram", "audio/x-pn-realaudio" }, + { ".rar", "application/x-rar-compressed" }, + { ".ras", "image/x-cmu-raster" }, + { ".rat", "application/rat-file" }, + { ".rc", "text/plain" }, + { ".rc2", "text/plain" }, + { ".rct", "text/plain" }, + { ".rdlc", "application/xml" }, + { ".reg", "text/plain" }, + { ".resx", "application/xml" }, + { ".rf", "image/vnd.rn-realflash" }, + { ".rgb", "image/x-rgb" }, + { ".rgs", "text/plain" }, + { ".rm", "application/vnd.rn-realmedia" }, + { ".rmi", "audio/mid" }, + { ".rmp", "application/vnd.rn-rn_music_package" }, + { ".roff", "application/x-troff" }, + { ".rpm", "audio/x-pn-realaudio-plugin" }, + { ".rqy", "text/x-ms-rqy" }, + { ".rtf", "application/rtf" }, + { ".rtx", "text/richtext" }, + { ".ruleset", "application/xml" }, + { ".s", "text/plain" }, + { ".safariextz", "application/x-safari-safariextz" }, + { ".scd", "application/x-msschedule" }, + { ".scr", "text/plain" }, + { ".sct", "text/scriptlet" }, + { ".sd2", "audio/x-sd2" }, + { ".sdp", "application/sdp" }, + { ".searchConnector-ms", "application/windows-search-connector+xml" }, + { ".setpay", "application/set-payment-initiation" }, + { ".setreg", "application/set-registration-initiation" }, + { ".settings", "application/xml" }, + { ".sgimb", "application/x-sgimb" }, + { ".sgml", "text/sgml" }, + { ".sh", "application/x-sh" }, + { ".shar", "application/x-shar" }, + { ".shtml", "text/html" }, + { ".sit", "application/x-stuffit" }, + { ".sitemap", "application/xml" }, + { ".skin", "application/xml" }, + { ".skp", "application/x-koan" }, + { ".sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12" }, + { ".sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide" }, + { ".slk", "application/vnd.ms-excel" }, + { ".sln", "text/plain" }, + { ".slupkg-ms", "application/x-ms-license" }, + { ".smd", "audio/x-smd" }, + { ".smx", "audio/x-smd" }, + { ".smz", "audio/x-smd" }, + { ".snd", "audio/basic" }, + { ".snippet", "application/xml" }, + { ".sol", "text/plain" }, + { ".sor", "text/plain" }, + { ".spc", "application/x-pkcs7-certificates" }, + { ".spl", "application/futuresplash" }, + { ".spx", "audio/ogg" }, + { ".src", "application/x-wais-source" }, + { ".srf", "text/plain" }, + { ".SSISDeploymentManifest", "text/xml" }, + { ".ssm", "application/streamingmedia" }, + { ".sst", "application/vnd.ms-pki.certstore" }, + { ".stl", "application/vnd.ms-pki.stl" }, + { ".sv4cpio", "application/x-sv4cpio" }, + { ".sv4crc", "application/x-sv4crc" }, + { ".svc", "application/xml" }, + { ".svg", "image/svg+xml" }, + { ".swf", "application/x-shockwave-flash" }, + { ".step", "application/step" }, + { ".stp", "application/step" }, + { ".t", "application/x-troff" }, + { ".tar", "application/x-tar" }, + { ".tcl", "application/x-tcl" }, + { ".testrunconfig", "application/xml" }, + { ".testsettings", "application/xml" }, + { ".tex", "application/x-tex" }, + { ".texi", "application/x-texinfo" }, + { ".texinfo", "application/x-texinfo" }, + { ".tgz", "application/x-compressed" }, + { ".thmx", "application/vnd.ms-officetheme" }, + { ".tif", "image/tiff" }, + { ".tiff", "image/tiff" }, + { ".tlh", "text/plain" }, + { ".tli", "text/plain" }, + { ".tr", "application/x-troff" }, + { ".trm", "application/x-msterminal" }, + { ".trx", "application/xml" }, + { ".ts", "video/vnd.dlna.mpeg-tts" }, + { ".tsv", "text/tab-separated-values" }, + { ".ttf", "application/font-sfnt" }, + { ".tts", "video/vnd.dlna.mpeg-tts" }, + { ".txt", "text/plain" }, + { ".uls", "text/iuls" }, + { ".user", "text/plain" }, + { ".ustar", "application/x-ustar" }, + { ".vb", "text/plain" }, + { ".vbdproj", "text/plain" }, + { ".vbk", "video/mpeg" }, + { ".vbproj", "text/plain" }, + { ".vbs", "text/vbscript" }, + { ".vcf", "text/x-vcard" }, + { ".vcproj", "application/xml" }, + { ".vcs", "text/plain" }, + { ".vcxproj", "application/xml" }, + { ".vddproj", "text/plain" }, + { ".vdp", "text/plain" }, + { ".vdproj", "text/plain" }, + { ".vdx", "application/vnd.ms-visio.viewer" }, + { ".vml", "text/xml" }, + { ".vscontent", "application/xml" }, + { ".vsct", "text/xml" }, + { ".vsd", "application/vnd.visio" }, + { ".vsi", "application/ms-vsi" }, + { ".vsix", "application/vsix" }, + { ".vsixlangpack", "text/xml" }, + { ".vsixmanifest", "text/xml" }, + { ".vsmdi", "application/xml" }, + { ".vspscc", "text/plain" }, + { ".vss", "application/vnd.visio" }, + { ".vsscc", "text/plain" }, + { ".vssettings", "text/xml" }, + { ".vssscc", "text/plain" }, + { ".vst", "application/vnd.visio" }, + { ".vstemplate", "text/xml" }, + { ".vsto", "application/x-ms-vsto" }, + { ".vsw", "application/vnd.visio" }, + { ".vsx", "application/vnd.visio" }, + { ".vtx", "application/vnd.visio" }, + { ".wav", "audio/wav" }, + { ".wave", "audio/wav" }, + { ".wax", "audio/x-ms-wax" }, + { ".wbk", "application/msword" }, + { ".wbmp", "image/vnd.wap.wbmp" }, + { ".wcm", "application/vnd.ms-works" }, + { ".wdb", "application/vnd.ms-works" }, + { ".wdp", "image/vnd.ms-photo" }, + { ".webarchive", "application/x-safari-webarchive" }, + { ".webm", "video/webm" }, + { ".webp", "image/webp" }, + { ".webtest", "application/xml" }, + { ".wiq", "application/xml" }, + { ".wiz", "application/msword" }, + { ".wks", "application/vnd.ms-works" }, + { ".WLMP", "application/wlmoviemaker" }, + { ".wlpginstall", "application/x-wlpg-detect" }, + { ".wlpginstall3", "application/x-wlpg3-detect" }, + { ".wm", "video/x-ms-wm" }, + { ".wma", "audio/x-ms-wma" }, + { ".wmd", "application/x-ms-wmd" }, + { ".wmf", "application/x-msmetafile" }, + { ".wml", "text/vnd.wap.wml" }, + { ".wmlc", "application/vnd.wap.wmlc" }, + { ".wmls", "text/vnd.wap.wmlscript" }, + { ".wmlsc", "application/vnd.wap.wmlscriptc" }, + { ".wmp", "video/x-ms-wmp" }, + { ".wmv", "video/x-ms-wmv" }, + { ".wmx", "video/x-ms-wmx" }, + { ".wmz", "application/x-ms-wmz" }, + { ".woff", "application/font-woff" }, + { ".wpl", "application/vnd.ms-wpl" }, + { ".wps", "application/vnd.ms-works" }, + { ".wri", "application/x-mswrite" }, + { ".wrl", "x-world/x-vrml" }, + { ".wrz", "x-world/x-vrml" }, + { ".wsc", "text/scriptlet" }, + { ".wsdl", "text/xml" }, + { ".wvx", "video/x-ms-wvx" }, + { ".x", "application/directx" }, + { ".xaf", "x-world/x-vrml" }, + { ".xaml", "application/xaml+xml" }, + { ".xap", "application/x-silverlight-app" }, + { ".xbap", "application/x-ms-xbap" }, + { ".xbm", "image/x-xbitmap" }, + { ".xdr", "text/plain" }, + { ".xht", "application/xhtml+xml" }, + { ".xhtml", "application/xhtml+xml" }, + { ".xla", "application/vnd.ms-excel" }, + { ".xlam", "application/vnd.ms-excel.addin.macroEnabled.12" }, + { ".xlc", "application/vnd.ms-excel" }, + { ".xld", "application/vnd.ms-excel" }, + { ".xlk", "application/vnd.ms-excel" }, + { ".xll", "application/vnd.ms-excel" }, + { ".xlm", "application/vnd.ms-excel" }, + { ".xls", "application/vnd.ms-excel" }, + { ".xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12" }, + { ".xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12" }, + { ".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }, + { ".xlt", "application/vnd.ms-excel" }, + { ".xltm", "application/vnd.ms-excel.template.macroEnabled.12" }, + { ".xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template" }, + { ".xlw", "application/vnd.ms-excel" }, + { ".xml", "text/xml" }, + { ".xmta", "application/xml" }, + { ".xof", "x-world/x-vrml" }, + { ".XOML", "text/plain" }, + { ".xpm", "image/x-xpixmap" }, + { ".xps", "application/vnd.ms-xpsdocument" }, + { ".xrm-ms", "text/xml" }, + { ".xsc", "application/xml" }, + { ".xsd", "text/xml" }, + { ".xsf", "text/xml" }, + { ".xsl", "text/xml" }, + { ".xslt", "text/xml" }, + { ".xss", "application/xml" }, + { ".xspf", "application/xspf+xml" }, + { ".xwd", "image/x-xwindowdump" }, + { ".z", "application/x-compress" }, + { ".zip", "application/zip" } + }; + } + + public static string MarshalXML(object obj, string nmspc) + { + if (obj is null) throw new ArgumentNullException(nameof(obj)); + + XmlWriter xw = null; + + var str = string.Empty; + + try + { + var settings = new XmlWriterSettings { OmitXmlDeclaration = true }; + var ns = new XmlSerializerNamespaces(); + ns.Add("", nmspc); + + using var sw = new StringWriter(CultureInfo.InvariantCulture); + + var xs = new XmlSerializer(obj.GetType()); + using (xw = XmlWriter.Create(sw, settings)) + { + xs.Serialize(xw, obj, ns); + xw.Flush(); + + str = sw.ToString(); + } + } + finally + { + xw.Close(); + } + + return str; + } + + public static string To8601String(DateTime dt) + { + return dt.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ssZ", CultureInfo.InvariantCulture); + } + + public static string RemoveNamespaceInXML(string config) + { + // We'll need to remove the namespace within the serialized configuration + const RegexOptions regexOptions = + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | + RegexOptions.Multiline; + var patternToReplace = + @"<\w+\s+\w+:nil=""true""(\s+xmlns:\w+=""http://www.w3.org/2001/XMLSchema-instance"")?\s*/>"; + const string patternToMatch = @"<\w+\s+xmlns=""http://s3.amazonaws.com/doc/2006-03-01/""\s*>"; + if (Regex.Match(config, patternToMatch, regexOptions, TimeSpan.FromHours(1)).Success) + patternToReplace = @"xmlns=""http://s3.amazonaws.com/doc/2006-03-01/""\s*"; + return Regex.Replace( + config, + patternToReplace, + string.Empty, + regexOptions, + TimeSpan.FromHours(1) + ); + } + + public static DateTime From8601String(string dt) + { + return DateTime.Parse(dt, null, DateTimeStyles.RoundtripKind); + } + + public static Uri GetBaseUrl(string endpoint) + { + if (string.IsNullOrEmpty(endpoint)) + throw new ArgumentException( + string.Format(CultureInfo.InvariantCulture, + "{0} is the value of the endpoint. It can't be null or empty.", endpoint), + nameof(endpoint)); + + if (endpoint.EndsWith("/", StringComparison.OrdinalIgnoreCase)) + endpoint = endpoint[..^1]; + if (!endpoint.StartsWith("http", StringComparison.OrdinalIgnoreCase) && + !BuilderUtil.IsValidHostnameOrIPAddress(endpoint)) + throw new InvalidEndpointException( + string.Format(CultureInfo.InvariantCulture, "{0} is invalid hostname.", endpoint), "endpoint"); + string conn_url; + if (endpoint.StartsWith("http", StringComparison.OrdinalIgnoreCase)) + throw new InvalidEndpointException( + string.Format(CultureInfo.InvariantCulture, + "{0} the value of the endpoint has the scheme (http/https) in it.", endpoint), + "endpoint"); + + var enable_https = Environment.GetEnvironmentVariable("ENABLE_HTTPS"); + var scheme = enable_https?.Equals("1", StringComparison.OrdinalIgnoreCase) == true ? "https://" : "http://"; + conn_url = scheme + endpoint; + var url = new Uri(conn_url); + var hostnameOfUri = url.Authority; + if (!string.IsNullOrWhiteSpace(hostnameOfUri) && !BuilderUtil.IsValidHostnameOrIPAddress(hostnameOfUri)) + throw new InvalidEndpointException( + string.Format(CultureInfo.InvariantCulture, "{0}, {1} is invalid hostname.", endpoint, hostnameOfUri), + "endpoint"); + + return url; + } + + internal static HttpRequestMessageBuilder GetEmptyRestRequest(HttpRequestMessageBuilder requestBuilder) + { + var serializedBody = JsonSerializer.Serialize(""); + requestBuilder.AddOrUpdateHeaderParameter("application/json; charset=utf-8", serializedBody); + return requestBuilder; + } + + // Converts an object to a byte array + public static ReadOnlyMemory ObjectToByteArray(object obj) + { + switch (obj) + { + case null: + case Memory memory when memory.IsEmpty: + case ReadOnlyMemory readOnlyMemory when readOnlyMemory.IsEmpty: + return null; + default: + return JsonSerializer.SerializeToUtf8Bytes(obj); + } + } + + // Print object key properties and their values + // Added for debugging purposes + + public static void ObjPrint(object obj) + { + foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(obj)) + { + var name = descriptor.Name; + var value = descriptor.GetValue(obj); + Console.WriteLine($"{name}={value}"); + } + } + + public static void Print(object obj) + { + if (obj is null) throw new ArgumentNullException(nameof(obj)); + + foreach (var prop in obj.GetType() + .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) + { + var value = prop.GetValue(obj, Array.Empty()); + Console.WriteLine("DEBUG >> {0} = {1}", prop.Name, value); + } + + Console.WriteLine("DEBUG >> Print is DONE!\n\n"); + } + + public static void PrintDict(IDictionary d) + { + if (d is not null) + foreach (var kv in d) + Console.WriteLine("DEBUG >> {0} = {1}", kv.Key, kv.Value); + + Console.WriteLine("DEBUG >> Done printing\n"); + } + + public static string DetermineNamespace(XDocument document) + { + if (document is null) throw new ArgumentNullException(nameof(document)); + + return document.Root.Attributes().FirstOrDefault(attr => attr.IsNamespaceDeclaration)?.Value ?? string.Empty; + } + + public static string SerializeToXml(T anyobject) where T : class + { + if (anyobject is null) throw new ArgumentNullException(nameof(anyobject)); + + var xs = new XmlSerializer(anyobject.GetType()); + using var sw = new StringWriter(CultureInfo.InvariantCulture); + using var xw = XmlWriter.Create(sw); + + xs.Serialize(xw, anyobject); + xw.Flush(); + + return sw.ToString(); + } + + public static T DeserializeXml(Stream stream) where T : class, new() + { + if (stream == null || stream.Length == 0) return default; + + var ns = GetNamespace(); + if (!string.IsNullOrWhiteSpace(ns) && string.Equals(ns, "http://s3.amazonaws.com/doc/2006-03-01/", + StringComparison.OrdinalIgnoreCase)) + { + using var amazonAwsS3XmlReader = new AmazonAwsS3XmlReader(stream); + return (T)new XmlSerializer(typeof(T)).Deserialize(amazonAwsS3XmlReader); + } + + using var reader = new StreamReader(stream); + var xmlContent = reader.ReadToEnd(); + + return DeserializeXml(xmlContent); // Call the string overload + } + + public static T DeserializeXml(string xml) where T : class, new() + { + if (string.IsNullOrEmpty(xml)) return default; + + var settings = new XmlReaderSettings + { + // Disable DTD processing + DtdProcessing = DtdProcessing.Prohibit, + // Disable XML schema validation + XmlResolver = null + }; + + using var stringReader = new StringReader(xml); + using var xmlReader = XmlReader.Create(stringReader, settings); + + try + { + var serializer = new XmlSerializer(typeof(T)); + return (T)serializer.Deserialize(xmlReader); + } + catch (InvalidOperationException) + { + return default; + } + } + + private static string GetNamespace() + { + if (typeof(T).GetCustomAttributes(typeof(XmlRootAttribute), true) + .FirstOrDefault() is XmlRootAttribute xmlRootAttribute) + return xmlRootAttribute.Namespace; + + return null; + } +} diff --git a/LibExternal/Minio/HttpRequestMessageBuilder.cs b/LibExternal/Minio/HttpRequestMessageBuilder.cs new file mode 100644 index 0000000..b941c1c --- /dev/null +++ b/LibExternal/Minio/HttpRequestMessageBuilder.cs @@ -0,0 +1,177 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2017, 2018, 2019, 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Globalization; +using System.Net.Http.Headers; +using System.Text; +using Minio.Exceptions; + +namespace Minio; + +internal class HttpRequestMessageBuilder +{ + internal HttpRequestMessageBuilder(Uri requestUri, HttpMethod method) + { + RequestUri = requestUri; + Method = method; + } + + public HttpRequestMessageBuilder(HttpMethod method, Uri host, string path) + : this(method, new UriBuilder(host) { Path = host.AbsolutePath + path }.Uri) + { + } + + public HttpRequestMessageBuilder(HttpMethod method, string requestUrl) + : this(method, new Uri(requestUrl)) + { + } + + public HttpRequestMessageBuilder(HttpMethod method, Uri requestUri) + { + Method = method; + RequestUri = requestUri; + + QueryParameters = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + HeaderParameters = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + BodyParameters = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + } + + public Uri RequestUri { get; set; } + public Action ResponseWriter { get; set; } + public Func FunctionResponseWriter { get; set; } + public HttpMethod Method { get; } + + public HttpRequestMessage Request + { + get + { + var requestUriBuilder = new UriBuilder(RequestUri); + var requestUri = requestUriBuilder.Uri; + var request = new HttpRequestMessage(Method, requestUri); + + if (!Content.IsEmpty) request.Content = new ReadOnlyMemoryContent(Content); + + foreach (var parameter in HeaderParameters) + { + var key = parameter.Key.ToLowerInvariant(); + var val = parameter.Value; + + var addSuccess = request.Headers.TryAddWithoutValidation(key, val); + if (!addSuccess) + { + request.Content ??= new StringContent(""); + switch (key) + { + case "content-type": + try + { + request.Content.Headers.ContentType = new MediaTypeHeaderValue(val); + } + catch + { + var success = request.Content.Headers.TryAddWithoutValidation(ContentTypeKey, val); + } + + break; + + case "content-length": + request.Content.Headers.ContentLength = Convert.ToInt32(val, CultureInfo.InvariantCulture); + break; + + case "content-md5": + request.Content.Headers.ContentMD5 = Convert.FromBase64String(val); + break; + + default: + var errMessage = "Unsupported signed header: (" + key + ": " + val; + throw new UnexpectedMinioException(errMessage); + } + } + } + + if (request.Content is not null) + { + var isMultiDeleteRequest = false; + if (Method == HttpMethod.Post) isMultiDeleteRequest = QueryParameters.ContainsKey("delete"); + var isSecure = RequestUri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase); + + if (!isSecure && !isMultiDeleteRequest && + BodyParameters.TryGetValue("Content-Md5", out var value) && value is not null) + { + _ = BodyParameters.TryGetValue("Content-Md5", out var returnValue); + request.Content.Headers.ContentMD5 = Convert.FromBase64String(returnValue); + } + } + + return request; + } + } + + public Dictionary QueryParameters { get; } + + public Dictionary HeaderParameters { get; } + + public Dictionary BodyParameters { get; } + + public ReadOnlyMemory Content { get; private set; } + + public string ContentTypeKey => "Content-Type"; + + public void AddHeaderParameter(string key, string value) + { + if (key.StartsWith("content-", StringComparison.InvariantCultureIgnoreCase) && + !string.IsNullOrEmpty(value) && + !BodyParameters.ContainsKey(key)) + BodyParameters.Add(key, value); + + HeaderParameters[key] = value; + } + + public void AddOrUpdateHeaderParameter(string key, string value) + { + if (HeaderParameters.GetType().GetProperty(key) is not null) + _ = HeaderParameters.Remove(key); + HeaderParameters[key] = value; + } + + public void AddBodyParameter(string key, string value) + { + BodyParameters.Add(key, value); + } + + public void AddQueryParameter(string key, string value) + { + QueryParameters[key] = value; + } + + public void SetBody(ReadOnlyMemory body) + { + Content = body; + } + + public void AddXmlBody(string body) + { + SetBody(Encoding.UTF8.GetBytes(body)); + BodyParameters.Add(ContentTypeKey, "application/xml"); + } + + public void AddJsonBody(string body) + { + SetBody(Encoding.UTF8.GetBytes(body)); + BodyParameters.Add(ContentTypeKey, "application/json"); + } +} diff --git a/LibExternal/Minio/IMinioClient.cs b/LibExternal/Minio/IMinioClient.cs new file mode 100644 index 0000000..fbbdce0 --- /dev/null +++ b/LibExternal/Minio/IMinioClient.cs @@ -0,0 +1,34 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.ApiEndpoints; +using Minio.Handlers; + +namespace Minio; + +public interface IMinioClient : IBucketOperations, IObjectOperations, IDisposable +{ + MinioConfig Config { get; } + IEnumerable ResponseErrorHandlers { get; } + IApiResponseErrorHandler DefaultErrorHandler { get; } + IRequestLogger RequestLogger { get; } + + void SetTraceOff(); + //void SetTraceOn(IRequestLogger logger = null); + Task WrapperGetAsync(Uri uri); + Task WrapperPutAsync(Uri uri, StreamContent strm); +} diff --git a/LibExternal/Minio/IMinioClientFactory.cs b/LibExternal/Minio/IMinioClientFactory.cs new file mode 100644 index 0000000..ed50042 --- /dev/null +++ b/LibExternal/Minio/IMinioClientFactory.cs @@ -0,0 +1,7 @@ +namespace Minio; + +public interface IMinioClientFactory +{ + IMinioClient CreateClient(); + IMinioClient CreateClient(Action configureClient); +} diff --git a/LibExternal/Minio/LoggingExtensions.cs b/LibExternal/Minio/LoggingExtensions.cs new file mode 100644 index 0000000..cc47709 --- /dev/null +++ b/LibExternal/Minio/LoggingExtensions.cs @@ -0,0 +1,49 @@ +using Minio.DataModel.Result; +using Minio.DataModel.Tracing; + +namespace Minio; + +public static class LoggingExtensions +{ + /// + /// Logs the request sent to server and corresponding response + /// + /// + /// + /// + /// + internal static void LogRequest(this IMinioClient minioClient, HttpRequestMessage request, ResponseResult response, + double durationMs) + { + var requestToLog = new RequestToLog + { + Resource = request.RequestUri.PathAndQuery, + // Parameters are custom anonymous objects in order to have the parameter type as a nice string + // otherwise it will just show the enum value + Parameters = request.Headers.Select(parameter => new RequestParameter + { + Name = parameter.Key, + Value = parameter.Value, + Type = typeof(KeyValuePair>).ToString() + }), + // ToString() here to have the method as a nice string otherwise it will just show the enum value + Method = request.Method.ToString(), + // This will generate the actual Uri used in the request + Uri = request.RequestUri + }; + + var responseToLog = new ResponseToLog + { + StatusCode = response.StatusCode, + Content = response.Content, + Headers = response.Headers.ToDictionary(o => o.Key, o => string.Join(Environment.NewLine, o.Value), + StringComparer.Ordinal), + // The Uri that actually responded (could be different from the requestUri if a redirection occurred) + ResponseUri = response.Request.RequestUri, + ErrorMessage = response.ErrorMessage, + DurationMs = durationMs + }; + + minioClient.RequestLogger.LogRequest(requestToLog, responseToLog, durationMs); + } +} diff --git a/LibExternal/Minio/Minio.csproj b/LibExternal/Minio/Minio.csproj new file mode 100644 index 0000000..bf0e9fd --- /dev/null +++ b/LibExternal/Minio/Minio.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/LibExternal/Minio/MinioClient.cs b/LibExternal/Minio/MinioClient.cs new file mode 100644 index 0000000..a16f6f9 --- /dev/null +++ b/LibExternal/Minio/MinioClient.cs @@ -0,0 +1,298 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2017-2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Net; +using System.Text; +using Minio.DataModel.Result; +using Minio.Exceptions; +using Minio.Handlers; +using Minio.Helper; + +namespace Minio; + +public partial class MinioClient : IMinioClient +{ + private static readonly char[] separator = { '/' }; + + private bool disposedValue; + + /// + /// Creates and returns an MinIO Client + /// + /// Client with no arguments to be used with other builder methods + public MinioClient() + { + } + + public MinioConfig Config { get; } = new(); + + public IEnumerable ResponseErrorHandlers { get; internal set; } = + Enumerable.Empty(); + + /// + /// Default error handling delegate + /// + public IApiResponseErrorHandler DefaultErrorHandler { get; internal set; } = new DefaultErrorHandler(); + + public IRequestLogger RequestLogger { get; internal set; } + + /// + /// Runs httpClient's GetAsync method + /// + public Task WrapperGetAsync(Uri uri) + { + return Config.HttpClient.GetAsync(uri); + } + + /// + /// Runs httpClient's PutObjectAsync method + /// + public Task WrapperPutAsync(Uri uri, StreamContent strm) + { + return Task.Run(async () => await Config.HttpClient.PutAsync(uri, strm).ConfigureAwait(false)); + } + + /// + /// Sets HTTP tracing On.Writes output to Console + /// + //public void SetTraceOn(IRequestLogger requestLogger = null) + //{ + // var logger = Config?.ServiceProvider?.GetRequiredService>(); + // RequestLogger = requestLogger ?? new DefaultRequestLogger(logger); + // Config.TraceHttp = true; + //} + + /// + /// Sets HTTP tracing Off. + /// + public void SetTraceOff() + { + Config.TraceHttp = false; + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Parse response errors if any and return relevant error messages + /// + /// + internal static void ParseError(ResponseResult response) + { + if (response is null) + throw new ConnectionException( + "Response is nil. Please report this issue https://github.com/minio/minio-dotnet/issues", response); + + if (HttpStatusCode.Redirect.Equals(response.StatusCode) || + HttpStatusCode.TemporaryRedirect.Equals(response.StatusCode) || + HttpStatusCode.MovedPermanently.Equals(response.StatusCode)) + throw new RedirectionException( + "Redirection detected. Please report this issue https://github.com/minio/minio-dotnet/issues"); + + if (string.IsNullOrWhiteSpace(response.Content)) + { + ParseErrorNoContent(response); + return; + } + + ParseErrorFromContent(response); + } + + private static void ParseErrorNoContent(ResponseResult response) + { + if (HttpStatusCode.Forbidden.Equals(response.StatusCode) + || HttpStatusCode.BadRequest.Equals(response.StatusCode) + || HttpStatusCode.NotFound.Equals(response.StatusCode) + || HttpStatusCode.MethodNotAllowed.Equals(response.StatusCode) + || HttpStatusCode.NotImplemented.Equals(response.StatusCode)) + ParseWellKnownErrorNoContent(response); + +#pragma warning disable MA0099 // Use Explicit enum value instead of 0 + if (response.StatusCode == 0) + throw new ConnectionException("Connection error:" + response.ErrorMessage, response); +#pragma warning restore MA0099 // Use Explicit enum value instead of 0 + throw new InternalClientException( + "Unsuccessful response from server without XML:" + response.ErrorMessage, response); + } + + private static void ParseWellKnownErrorNoContent(ResponseResult response) + { + MinioException error = null; + var errorResponse = new ErrorResponse(); + + foreach (var parameter in response.Headers) + { + if (parameter.Key.Equals("x-amz-id-2", StringComparison.OrdinalIgnoreCase)) + errorResponse.HostId = parameter.Value; + + if (parameter.Key.Equals("x-amz-request-id", StringComparison.OrdinalIgnoreCase)) + errorResponse.RequestId = parameter.Value; + + if (parameter.Key.Equals("x-amz-bucket-region", StringComparison.OrdinalIgnoreCase)) + errorResponse.BucketRegion = parameter.Value; + } + + var pathAndQuery = response.Request.RequestUri.PathAndQuery; + var host = response.Request.RequestUri.Host; + errorResponse.Resource = pathAndQuery; + + // zero, one or two segments + var resourceSplits = pathAndQuery.Split(separator, 2, StringSplitOptions.RemoveEmptyEntries); + + if (HttpStatusCode.NotFound.Equals(response.StatusCode)) + { + var pathLength = resourceSplits.Length; + var isAWS = host.EndsWith("s3.amazonaws.com", StringComparison.OrdinalIgnoreCase); + var isVirtual = isAWS && !host.StartsWith("s3.amazonaws.com", StringComparison.OrdinalIgnoreCase); + + if (pathLength > 1) + { + var objectName = resourceSplits[1]; + errorResponse.Code = "NoSuchKey"; + error = new ObjectNotFoundException(objectName, "Not found."); + } + else if (pathLength == 1) + { + var resource = resourceSplits[0]; + + if (isAWS && isVirtual && !string.IsNullOrEmpty(pathAndQuery)) + { + errorResponse.Code = "NoSuchKey"; + error = new ObjectNotFoundException(resource, "Not found."); + } + else + { + errorResponse.Code = "NoSuchBucket"; + BucketRegionCache.Instance.Remove(resource); + error = new BucketNotFoundException(resource, "Not found."); + } + } + else + { + error = new InternalClientException("404 without body resulted in path with less than two components", + response); + } + } + else if (HttpStatusCode.BadRequest.Equals(response.StatusCode)) + { + var pathLength = resourceSplits.Length; + + if (pathLength > 1) + { + var objectName = resourceSplits[1]; + errorResponse.Code = "InvalidObjectName"; + error = new InvalidObjectNameException(objectName, "Invalid object name."); + } + else + { + error = new InternalClientException("400 without body resulted in path with less than two components", + response); + } + } + else if (HttpStatusCode.Forbidden.Equals(response.StatusCode)) + { + errorResponse.Code = "Forbidden"; + error = new AccessDeniedException("Access denied on the resource: " + pathAndQuery); + } + + error.Response = errorResponse; + throw error; + } + + private static void ParseErrorFromContent(ResponseResult response) + { + if (response is null) + throw new ArgumentNullException(nameof(response)); + + if (response.StatusCode.Equals(HttpStatusCode.NotFound) + && response.Request.RequestUri.PathAndQuery.EndsWith("?location", StringComparison.OrdinalIgnoreCase) + && response.Request.Method.Equals(HttpMethod.Get)) + { + var bucketName = response.Request.RequestUri.PathAndQuery.Split('?')[0]; + BucketRegionCache.Instance.Remove(bucketName); + throw new BucketNotFoundException(bucketName, "Not found."); + } + + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(response.Content).AsMemory().ToArray()); + var errResponse = Utils.DeserializeXml(stream); + + if (response.StatusCode.Equals(HttpStatusCode.Forbidden) + && (errResponse.Code.Equals("SignatureDoesNotMatch", StringComparison.OrdinalIgnoreCase) || + errResponse.Code.Equals("InvalidAccessKeyId", StringComparison.OrdinalIgnoreCase))) + throw new AuthorizationException(errResponse.Resource, errResponse.BucketName, errResponse.Message); + + // Handle XML response for Bucket Policy not found case + if (response.StatusCode.Equals(HttpStatusCode.NotFound) + && response.Request.RequestUri.PathAndQuery.EndsWith("?policy", StringComparison.OrdinalIgnoreCase) + && response.Request.Method.Equals(HttpMethod.Get) + && string.Equals(errResponse.Code, "NoSuchBucketPolicy", StringComparison.OrdinalIgnoreCase)) + throw new ErrorResponseException(errResponse, response) { XmlError = response.Content }; + + if (response.StatusCode.Equals(HttpStatusCode.NotFound) + && string.Equals(errResponse.Code, "NoSuchBucket", StringComparison.OrdinalIgnoreCase)) + throw new BucketNotFoundException(errResponse.BucketName, "Not found."); + + if (response.StatusCode.Equals(HttpStatusCode.BadRequest) + && errResponse.Code.Equals("MalformedXML", StringComparison.OrdinalIgnoreCase)) + throw new MalFormedXMLException(errResponse.Resource, errResponse.BucketName, errResponse.Message, + errResponse.Key); + + if (response.StatusCode.Equals(HttpStatusCode.NotImplemented) + && errResponse.Code.Equals("NotImplemented", StringComparison.OrdinalIgnoreCase)) +#pragma warning disable MA0025 // Implement the functionality instead of throwing NotImplementedException + throw new NotImplementedException(errResponse.Message); +#pragma warning restore MA0025 // Implement the functionality instead of throwing NotImplementedException + + if (response.StatusCode.Equals(HttpStatusCode.BadRequest) + && errResponse.Code.Equals("InvalidRequest", StringComparison.OrdinalIgnoreCase)) + { + var legalHold = new Dictionary(StringComparer.Ordinal) { { "legal-hold", "" } }; + if (response.Request.RequestUri.Query.Contains("legalHold", StringComparison.OrdinalIgnoreCase)) + throw new MissingObjectLockConfigurationException(errResponse.BucketName, errResponse.Message); + } + + if (response.StatusCode.Equals(HttpStatusCode.NotFound) + && errResponse.Code.Equals("ObjectLockConfigurationNotFoundError", StringComparison.OrdinalIgnoreCase)) + throw new MissingObjectLockConfigurationException(errResponse.BucketName, errResponse.Message); + + if (response.StatusCode.Equals(HttpStatusCode.NotFound) + && errResponse.Code.Equals("ReplicationConfigurationNotFoundError", StringComparison.OrdinalIgnoreCase)) + throw new MissingBucketReplicationConfigurationException(errResponse.BucketName, errResponse.Message); + + if (response.StatusCode.Equals(HttpStatusCode.Conflict) + && errResponse.Code.Equals("BucketAlreadyOwnedByYou", StringComparison.OrdinalIgnoreCase)) + throw new ArgumentException("Bucket already owned by you: " + errResponse.BucketName, + nameof(response)); + + throw new UnexpectedMinioException(errResponse.Message) { Response = errResponse, XmlError = response.Content }; + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + if (Config.DisposeHttpClient) + Config.HttpClient?.Dispose(); + disposedValue = true; + } + } +} diff --git a/LibExternal/Minio/MinioClientExtensions.cs b/LibExternal/Minio/MinioClientExtensions.cs new file mode 100644 index 0000000..b6e0f45 --- /dev/null +++ b/LibExternal/Minio/MinioClientExtensions.cs @@ -0,0 +1,334 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Globalization; +using System.Net; +using System.Reflection; +using System.Runtime.InteropServices; +using Minio.Credentials; +using Minio.DataModel; +using Minio.DataModel.Result; +using Minio.Exceptions; +using Minio.Handlers; +using Minio.Helper; + +namespace Minio; + +public static class MinioClientExtensions +{ + internal static string SystemUserAgent + { + get + { + var assembly = Assembly.GetExecutingAssembly(); + var version = assembly.GetName().Version; + var release = $"minio-dotnet/{version}"; +#if NET46 + string arch = Environment.Is64BitOperatingSystem ? "x86_64" : "x86"; + return $"MinIO ({Environment.OSVersion};{arch}) {release}"; +#else + var arch = RuntimeInformation.OSArchitecture.ToString(); + return $"MinIO ({RuntimeInformation.OSDescription};{arch}) {release}"; +#endif + } + } + + public static IMinioClient WithEndpoint(this IMinioClient minioClient, string endpoint) + { + if (minioClient is null) throw new ArgumentNullException(nameof(minioClient)); + + minioClient.Config.Endpoint = endpoint; + minioClient.SetBaseURL(GetBaseUrl(endpoint)); + return minioClient; + } + + public static IMinioClient WithEndpoint(this IMinioClient minioClient, string endpoint, int port) + { + if (minioClient is null) throw new ArgumentNullException(nameof(minioClient)); + + if (port < 1 || port > 65535) + throw new ArgumentException( + string.Format(CultureInfo.InvariantCulture, "Port {0} is not a number between 1 and 65535", port), + nameof(port)); + endpoint = endpoint + ":" + port.ToString(CultureInfo.InvariantCulture); + minioClient.Config.Endpoint = endpoint; + minioClient.SetBaseURL(GetBaseUrl(endpoint)); + return minioClient; + } + + public static IMinioClient WithEndpoint(this IMinioClient minioClient, Uri url) + { + if (minioClient is null) throw new ArgumentNullException(nameof(minioClient)); + + if (url is null) throw new ArgumentNullException(nameof(url)); + minioClient.SetBaseURL(url); + minioClient.Config.Endpoint = url.AbsoluteUri; + + return minioClient; + } + + public static IMinioClient WithRegion(this IMinioClient minioClient, string region) + { + if (minioClient is null) throw new ArgumentNullException(nameof(minioClient)); + + if (string.IsNullOrEmpty(region)) + throw new ArgumentException( + string.Format(CultureInfo.InvariantCulture, "{0} the region value can't be null or empty.", region), + nameof(region)); + + minioClient.Config.Region = region; + return minioClient; + } + + public static IMinioClient WithRegion(this IMinioClient minioClient) + { + if (minioClient is null) throw new ArgumentNullException(nameof(minioClient)); + // Set region to its default value if empty or null + minioClient.Config.Region ??= "us-east-1"; + return minioClient; + } + + public static IMinioClient WithCredentials(this IMinioClient minioClient, string accessKey, string secretKey) + { + if (minioClient is null) throw new ArgumentNullException(nameof(minioClient)); + + minioClient.Config.AccessKey = accessKey; + minioClient.Config.SecretKey = secretKey; + return minioClient; + } + + public static IMinioClient WithSessionToken(this IMinioClient minioClient, string st) + { + if (minioClient is null) throw new ArgumentNullException(nameof(minioClient)); + + minioClient.Config.SessionToken = st; + return minioClient; + } + + /// + /// Connects to Cloud Storage with HTTPS if this method is invoked on client object + /// + /// + public static IMinioClient WithSSL(this IMinioClient minioClient, bool secure = true) + { + if (minioClient is null) throw new ArgumentNullException(nameof(minioClient)); + minioClient.Config.Secure = secure; + return minioClient; + } + + /// + /// Uses webproxy for all requests if this method is invoked on client object. + /// + /// The MinioClient instance used + /// Information on the proxy server in the setup. + /// + public static IMinioClient WithProxy(this IMinioClient minioClient, IWebProxy proxy) + { + if (minioClient is null) throw new ArgumentNullException(nameof(minioClient)); + + minioClient.Config.Proxy = proxy; + return minioClient; + } + + /// + /// Uses the set timeout for all requests if this method is invoked on client object + /// + /// The MinioClient instance used + /// Timeout in milliseconds. + /// + public static IMinioClient WithTimeout(this IMinioClient minioClient, int timeout) + { + if (minioClient is null) throw new ArgumentNullException(nameof(minioClient)); + + minioClient.Config.RequestTimeout = timeout; + return minioClient; + } + + /// + /// Allows to add retry policy handler + /// + /// The MinioClient instance used + /// Delegate that will wrap execution of http client requests. + /// + public static IMinioClient WithRetryPolicy(this IMinioClient minioClient, + IRetryPolicyHandler retryPolicyHandler) + { + if (minioClient is null) throw new ArgumentNullException(nameof(minioClient)); + + minioClient.Config.RetryPolicyHandler = retryPolicyHandler; + return minioClient; + } + + /// + /// Allows to add retry policy handler + /// + /// The MinioClient instance used + /// Delegate that will wrap execution of http client requests. + /// + public static IMinioClient WithRetryPolicy(this IMinioClient minioClient, + Func>, Task> retryPolicyHandler) + { + if (minioClient is null) throw new ArgumentNullException(nameof(minioClient)); + + _ = minioClient.WithRetryPolicy(new DefaultRetryPolicyHandler(retryPolicyHandler)); + return minioClient; + } + + /// + /// Allows end user to define the Http server and pass it as a parameter + /// + /// The MinioClient instance used + /// Instance of HttpClient + /// Dispose the HttpClient when leaving + /// + public static IMinioClient WithHttpClient(this IMinioClient minioClient, HttpClient httpClient, + bool disposeHttpClient = false) + { + if (minioClient is null) throw new ArgumentNullException(nameof(minioClient)); + + if (httpClient is not null) minioClient.Config.HttpClient = httpClient; + minioClient.Config.DisposeHttpClient = disposeHttpClient; + return minioClient; + } + + /// + /// With provider for credentials and session token if being used + /// + /// + public static IMinioClient WithCredentialsProvider(this IMinioClient minioClient, IClientProvider provider) + { + if (minioClient is null) throw new ArgumentNullException(nameof(minioClient)); + + minioClient.Config.Provider = provider; + AccessCredentials credentials; + if (minioClient.Config.Provider is IAMAWSProvider) + // Empty object, we need the Minio client completely + credentials = new AccessCredentials(); + else + credentials = minioClient.Config.Provider.GetCredentials(); + + if (credentials is null) + // Unable to fetch credentials. + return minioClient; + + minioClient.Config.AccessKey = credentials.AccessKey; + minioClient.Config.SecretKey = credentials.SecretKey; + var isSessionTokenAvailable = !string.IsNullOrEmpty(credentials.SessionToken); + if ((minioClient.Config.Provider is AWSEnvironmentProvider || + minioClient.Config.Provider is IAMAWSProvider || + minioClient.Config.Provider is CertificateIdentityProvider || + (minioClient.Config.Provider is ChainedProvider chainedProvider && + chainedProvider.CurrentProvider is AWSEnvironmentProvider)) + && isSessionTokenAvailable) + minioClient.Config.SessionToken = credentials.SessionToken; + + return minioClient; + } + + public static IMinioClient Build(this IMinioClient minioClient) + { + if (minioClient is null) throw new ArgumentNullException(nameof(minioClient)); + + // Instantiate a region cache + minioClient.Config.RegionCache = BucketRegionCache.Instance; + if (string.IsNullOrEmpty(minioClient.Config.BaseUrl)) throw new MinioException("Endpoint not initialized."); + if (minioClient.Config.Provider is not null && + minioClient.Config.Provider.GetType() != typeof(ChainedProvider) && + minioClient.Config.SessionToken is null) + throw new MinioException("User Access Credentials Provider not initialized correctly."); + if (minioClient.Config.Provider is null && + (string.IsNullOrEmpty(minioClient.Config.AccessKey) || string.IsNullOrEmpty(minioClient.Config.SecretKey))) + throw new MinioException("User Access Credentials not initialized."); + + var host = minioClient.Config.BaseUrl; + + var scheme = minioClient.Config.Secure ? Utils.UrlEncode("https") : Utils.UrlEncode("http"); + + if (!minioClient.Config.BaseUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase)) + minioClient.Config.Endpoint = string.Format(CultureInfo.InvariantCulture, "{0}://{1}", scheme, host); + else + minioClient.Config.Endpoint = host; + + var httpClientHandler = new HttpClientHandler { Proxy = minioClient.Config.Proxy }; + minioClient.Config.HttpClient ??= minioClient.Config.Proxy is null + ? new HttpClient() + : new HttpClient(httpClientHandler); + _ = minioClient.Config.HttpClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", + minioClient.Config.FullUserAgent); + minioClient.Config.HttpClient.Timeout = TimeSpan.FromMinutes(30); + return minioClient; + } + + /// + /// Sets app version and name. Used for constructing User-Agent header in all HTTP requests + /// + /// + /// + /// + public static IMinioClient SetAppInfo(this IMinioClient minioClient, string appName, string appVersion) + { + if (string.IsNullOrEmpty(appName)) + throw new ArgumentException("Appname cannot be null or empty", nameof(appName)); + + if (string.IsNullOrEmpty(appVersion)) + throw new ArgumentException("Appversion cannot be null or empty", nameof(appVersion)); + + minioClient.Config.CustomUserAgent = $"{appName}/{appVersion}"; + + return minioClient; + } + + internal static Uri GetBaseUrl(string endpoint) + { + if (string.IsNullOrEmpty(endpoint)) + throw new ArgumentException( + string.Format(CultureInfo.InvariantCulture, + "{0} is the value of the endpoint. It can't be null or empty.", endpoint), + nameof(endpoint)); + + if (endpoint.EndsWith("/", StringComparison.OrdinalIgnoreCase)) + endpoint = endpoint[..^1]; + if (!BuilderUtil.IsValidHostnameOrIPAddress(endpoint)) + throw new InvalidEndpointException( + string.Format(CultureInfo.InvariantCulture, "{0} is invalid hostname.", endpoint), "endpoint"); + string conn_url; + if (endpoint.StartsWith("http", StringComparison.OrdinalIgnoreCase)) + throw new InvalidEndpointException( + string.Format(CultureInfo.InvariantCulture, + "{0} the value of the endpoint has the scheme (http/https) in it.", endpoint), + "endpoint"); + + var enable_https = Environment.GetEnvironmentVariable("ENABLE_HTTPS"); + var scheme = enable_https?.Equals("1", StringComparison.OrdinalIgnoreCase) == true ? "https://" : "http://"; + conn_url = scheme + endpoint; + var url = new Uri(conn_url); + var hostnameOfUri = url.Authority; + if (!string.IsNullOrEmpty(hostnameOfUri) && !BuilderUtil.IsValidHostnameOrIPAddress(hostnameOfUri)) + throw new InvalidEndpointException( + string.Format(CultureInfo.InvariantCulture, "{0}, {1} is invalid hostname.", endpoint, hostnameOfUri), + "endpoint"); + + return url; + } + + internal static void SetBaseURL(this IMinioClient minioClient, Uri url) + { + if (url.IsDefaultPort) + minioClient.Config.BaseUrl = url.Host; + else + minioClient.Config.BaseUrl = url.Host + ":" + url.Port; + } +} diff --git a/LibExternal/Minio/MinioClientFactory.cs b/LibExternal/Minio/MinioClientFactory.cs new file mode 100644 index 0000000..5fe8b7a --- /dev/null +++ b/LibExternal/Minio/MinioClientFactory.cs @@ -0,0 +1,52 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Minio; + +public class MinioClientFactory : IMinioClientFactory +{ + private const string DefaultEndpoint = "play.min.io"; + private readonly Action defaultConfigureClient; + + public MinioClientFactory(Action defaultConfigureClient) + { + this.defaultConfigureClient = + defaultConfigureClient ?? throw new ArgumentNullException(nameof(defaultConfigureClient)); + } + + public IMinioClient CreateClient() + { + return CreateClient(defaultConfigureClient); + } + + public IMinioClient CreateClient(Action configureClient) + { + if (configureClient == null) throw new ArgumentNullException(nameof(configureClient)); + + var client = new MinioClient() + .WithSSL(); + + configureClient(client); + + if (string.IsNullOrEmpty(client.Config.Endpoint)) + _ = client.WithEndpoint(DefaultEndpoint); + + _ = client.Build(); + + + return client; + } +} diff --git a/LibExternal/Minio/MinioConfig.cs b/LibExternal/Minio/MinioConfig.cs new file mode 100644 index 0000000..77f47bb --- /dev/null +++ b/LibExternal/Minio/MinioConfig.cs @@ -0,0 +1,53 @@ +using System.Net; +using Minio.Credentials; +using Minio.Handlers; + +namespace Minio; + +public class MinioConfig +{ + //internal ServiceProvider ServiceProvider { get; set; } + + // Cache holding bucket to region mapping for buckets seen so far. + public BucketRegionCache RegionCache { get; internal set; } + + public IClientProvider Provider { get; internal set; } + public HttpClient HttpClient { get; internal set; } + public IWebProxy Proxy { get; internal set; } + + // Handler for task retry policy + public IRetryPolicyHandler RetryPolicyHandler { get; internal set; } + + //TODO: Should be removed? + // Corresponding URI for above endpoint + public Uri Uri { get; internal set; } + + public bool DisposeHttpClient { get; internal set; } = true; + + // Save Credentials from user + public string AccessKey { get; internal set; } + public string SecretKey { get; internal set; } + public string BaseUrl { get; internal set; } + + // Reconstructed endpoint with scheme and host.In the case of Amazon, this url + // is the virtual style path or location based endpoint + public string Endpoint { get; internal set; } + public string SessionToken { get; internal set; } + + // Indicates if we are using HTTPS or not + public bool Secure { get; internal set; } + + public string Region { get; internal set; } + + public int RequestTimeout { get; internal set; } + + // Enables HTTP tracing if set to true + public bool TraceHttp { get; internal set; } + + public string CustomUserAgent { get; internal set; } = string.Empty; + + /// + /// Returns the User-Agent header for the request + /// + public string FullUserAgent => $"{MinioClientExtensions.SystemUserAgent} {CustomUserAgent}"; +} diff --git a/LibExternal/Minio/RequestExtensions.cs b/LibExternal/Minio/RequestExtensions.cs new file mode 100644 index 0000000..827d0d4 --- /dev/null +++ b/LibExternal/Minio/RequestExtensions.cs @@ -0,0 +1,372 @@ +using System.Diagnostics.CodeAnalysis; +using System.Net; +using Minio.Credentials; +using Minio.DataModel; +using Minio.DataModel.Args; +using Minio.DataModel.Result; +using Minio.Exceptions; +using Minio.Handlers; +using Minio.Helper; + +namespace Minio; + +public static class RequestExtensions +{ + [SuppressMessage("Design", "CA1054:URI-like parameters should not be strings", + Justification = "This is done in the interface. String is provided here for convenience")] + public static Task WrapperGetAsync(this IMinioClient minioClient, string url) + { + if (minioClient is null) throw new ArgumentNullException(nameof(minioClient)); + + return minioClient.WrapperGetAsync(new Uri(url)); + } + + /// + /// Runs httpClient's PutObjectAsync method + /// + [SuppressMessage("Design", "CA1054:URI-like parameters should not be strings", + Justification = "This is done in the interface. String is provided here for convenience")] + public static Task WrapperPutAsync(this IMinioClient minioClient, string url, StreamContent strm) + { + if (minioClient is null) throw new ArgumentNullException(nameof(minioClient)); + + return minioClient.WrapperPutAsync(new Uri(url), strm); + } + + /// + /// Actual doer that executes the request on the server + /// + /// + /// List of handlers to override default handling + /// The build of HttpRequestMessageBuilder + /// boolean; if true role credentials, otherwise IAM user + /// Optional cancellation token to cancel the operation + /// ResponseResult + internal static Task ExecuteTaskAsync(this IMinioClient minioClient, + IEnumerable errorHandlers, + HttpRequestMessageBuilder requestMessageBuilder, + bool isSts = false, + CancellationToken cancellationToken = default) + { + Task responseResult; + try + { + if (minioClient.Config.RequestTimeout > 0) + { + using var internalTokenSource = + new CancellationTokenSource(new TimeSpan(0, 0, 0, 0, minioClient.Config.RequestTimeout)); + using var timeoutTokenSource = + CancellationTokenSource.CreateLinkedTokenSource(internalTokenSource.Token, cancellationToken); + cancellationToken = timeoutTokenSource.Token; + } + + responseResult = minioClient.ExecuteWithRetry( + async () => await minioClient.ExecuteTaskCoreAsync(errorHandlers, requestMessageBuilder, + isSts, cancellationToken).ConfigureAwait(false)); + } + catch (Exception ex) + { + Console.WriteLine($"\n\n *** ExecuteTaskAsync::Threw an exception => {ex.Message}"); + throw; + } + + return responseResult; + } + + private static async Task ExecuteTaskCoreAsync(this IMinioClient minioClient, + IEnumerable errorHandlers, + HttpRequestMessageBuilder requestMessageBuilder, + bool isSts = false, + CancellationToken cancellationToken = default) + { + var startTime = DateTime.Now; + + var v4Authenticator = new V4Authenticator(minioClient.Config.Secure, + minioClient.Config.AccessKey, minioClient.Config.SecretKey, minioClient.Config.Region, + minioClient.Config.SessionToken); + + requestMessageBuilder.AddOrUpdateHeaderParameter("Authorization", + v4Authenticator.Authenticate(requestMessageBuilder, isSts)); + + var request = requestMessageBuilder.Request; + + ResponseResult responseResult = null; + try + { + var response = await minioClient.Config.HttpClient.SendAsync(request, + HttpCompletionOption.ResponseHeadersRead, cancellationToken) + .ConfigureAwait(false); + responseResult = new ResponseResult(request, response); + if (requestMessageBuilder.ResponseWriter is not null) + requestMessageBuilder.ResponseWriter(responseResult.ContentStream); + if (requestMessageBuilder.FunctionResponseWriter is not null) + await requestMessageBuilder.FunctionResponseWriter(responseResult.ContentStream, cancellationToken) + .ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + responseResult?.Dispose(); + responseResult = new ResponseResult(request, e); + } + + if (responseResult.StatusCode == HttpStatusCode.NotFound) + { + if (request.Method == HttpMethod.Head) + { + Exception ex = new BucketNotFoundException(); + responseResult.Exception = ex; + return responseResult; + } + + if (request.RequestUri.ToString().Contains("lock", StringComparison.OrdinalIgnoreCase) && + request.Method == HttpMethod.Get) + { + Exception ex = new MissingObjectLockConfigurationException(); + responseResult.Exception = ex; + return responseResult; + } + } + + minioClient.HandleIfErrorResponse(responseResult, errorHandlers, startTime); + return responseResult; + } + + private static Task ExecuteWithRetry(this IMinioClient minioClient, + Func> executeRequestCallback) + { + return minioClient.Config.RetryPolicyHandler is null + ? executeRequestCallback() + : minioClient.Config.RetryPolicyHandler.Handle(executeRequestCallback); + } + + /// + /// Constructs a HttpRequestMessageBuilder using bucket/object names from Args. + /// Calls overloaded CreateRequest method. + /// + /// + /// The direct descendant of BucketArgs class, args with populated values from Input + /// A HttpRequestMessageBuilder + internal static async Task CreateRequest(this IMinioClient minioClient, + BucketArgs args) where T : BucketArgs + { + ArgsCheck(args); + var requestMessageBuilder = + await minioClient.CreateRequest(args.RequestMethod, args.BucketName, headerMap: args.Headers, + isBucketCreationRequest: args.IsBucketCreationRequest).ConfigureAwait(false); + return args.BuildRequest(requestMessageBuilder); + } + + /// + /// Constructs a HttpRequestMessage using bucket/object names from Args. + /// Calls overloaded CreateRequest method. + /// + /// + /// The direct descendant of ObjectArgs class, args with populated values from Input + /// A HttpRequestMessage + internal static async Task CreateRequest(this IMinioClient minioClient, + ObjectArgs args) where T : ObjectArgs + { + ArgsCheck(args); + + var contentType = "application/octet-stream"; + _ = args.Headers?.TryGetValue("Content-Type", out contentType); + var requestMessageBuilder = + await minioClient.CreateRequest(args.RequestMethod, + args.BucketName, + args.ObjectName, + args.Headers, + contentType, + args.RequestBody).ConfigureAwait(false); + return args.BuildRequest(requestMessageBuilder); + } + + /// + /// Constructs an HttpRequestMessage builder. For AWS, this function + /// has the side-effect of overriding the baseUrl in the HttpClient + /// with region specific host path or virtual style path. + /// + /// + /// HTTP method + /// Bucket Name + /// Object Name + /// headerMap + /// Content Type + /// request body + /// query string + /// boolean to define bucket creation + /// A HttpRequestMessage builder + /// When bucketName is invalid + internal static async Task CreateRequest(this IMinioClient minioClient, + HttpMethod method, + string bucketName = null, + string objectName = null, + IDictionary headerMap = null, + string contentType = "application/octet-stream", + ReadOnlyMemory body = default, + string resourcePath = null, + bool isBucketCreationRequest = false) + { + var region = string.Empty; + if (bucketName is not null) + { + Utils.ValidateBucketName(bucketName); + // Fetch correct region for bucket if this is not a bucket creation + if (!isBucketCreationRequest) + region = await minioClient.GetRegion(bucketName).ConfigureAwait(false); + } + + if (objectName is not null) Utils.ValidateObjectName(objectName); + + if (minioClient.Config.Provider is not null) + { + var isAWSEnvProvider = minioClient.Config.Provider is AWSEnvironmentProvider || + (minioClient.Config.Provider is ChainedProvider ch && + ch.CurrentProvider is AWSEnvironmentProvider); + + var isIAMAWSProvider = minioClient.Config.Provider is IAMAWSProvider || + (minioClient.Config.Provider is ChainedProvider chained && + chained.CurrentProvider is AWSEnvironmentProvider); + + AccessCredentials creds; + if (isAWSEnvProvider) + { + var aWSEnvProvider = (AWSEnvironmentProvider)minioClient.Config.Provider; + creds = await aWSEnvProvider.GetCredentialsAsync().ConfigureAwait(false); + } + else if (isIAMAWSProvider) + { + var iamAWSProvider = (IAMAWSProvider)minioClient.Config.Provider; + creds = iamAWSProvider.Credentials; + } + else + { + creds = await minioClient.Config.Provider.GetCredentialsAsync().ConfigureAwait(false); + } + + if (creds is not null) + { + minioClient.Config.AccessKey = creds.AccessKey; + minioClient.Config.SecretKey = creds.SecretKey; + } + } + + // This section reconstructs the url with scheme followed by location specific endpoint (s3.region.amazonaws.com) + // or Virtual Host styled endpoint (bucketname.s3.region.amazonaws.com) for Amazon requests. + var resource = string.Empty; + var usePathStyle = false; + + if (!string.IsNullOrEmpty(bucketName) && S3utils.IsAmazonEndPoint(minioClient.Config.BaseUrl)) + { + if (method == HttpMethod.Put && objectName is null && resourcePath is null) + // use path style for make bucket to workaround "AuthorizationHeaderMalformed" error from s3.amazonaws.com + usePathStyle = true; + else if (resourcePath?.Contains("location", StringComparison.OrdinalIgnoreCase) == true) + // use path style for location query + usePathStyle = true; + else if (bucketName.Contains('.', StringComparison.Ordinal) && minioClient.Config.Secure) + // use path style where '.' in bucketName causes SSL certificate validation error + usePathStyle = true; + + if (usePathStyle) resource += Utils.UrlEncode(bucketName) + "/"; + } + + // Set Target URL + var requestUrl = RequestUtil.MakeTargetURL(minioClient.Config.BaseUrl, minioClient.Config.Secure, bucketName, + region, usePathStyle); + + if (objectName is not null) resource += Utils.EncodePath(objectName); + + // Append query string passed in + if (resourcePath is not null) resource += resourcePath; + + HttpRequestMessageBuilder messageBuilder; + if (!string.IsNullOrEmpty(resource)) + messageBuilder = new HttpRequestMessageBuilder(method, requestUrl, resource); + else + messageBuilder = new HttpRequestMessageBuilder(method, requestUrl); + if (!body.IsEmpty) + { + messageBuilder.SetBody(body); + messageBuilder.AddOrUpdateHeaderParameter("Content-Type", contentType); + } + + if (headerMap is not null) + { + if (headerMap.TryGetValue(messageBuilder.ContentTypeKey, out var value) && !string.IsNullOrEmpty(value)) + headerMap[messageBuilder.ContentTypeKey] = contentType; + + foreach (var entry in headerMap) messageBuilder.AddOrUpdateHeaderParameter(entry.Key, entry.Value); + } + + return messageBuilder; + } + + /// + /// Null Check for Args object. + /// Expected to be called from CreateRequest + /// + /// The child object of Args class + private static void ArgsCheck(RequestArgs args) + { + if (args is null) + throw new ArgumentNullException(nameof(args), + "Args object cannot be null. It needs to be assigned to an instantiated child object of Args."); + } + + /// + /// Resolve region of the bucket. + /// + /// + /// + /// + internal static async Task GetRegion(this IMinioClient minioClient, string bucketName) + { + var rgn = ""; + // Use user specified region in client constructor if present + if (!string.IsNullOrEmpty(minioClient.Config.Region)) return minioClient.Config.Region; + + // pick region from endpoint if present + if (!string.IsNullOrEmpty(minioClient.Config.Endpoint)) + rgn = RegionHelper.GetRegionFromEndpoint(minioClient.Config.Endpoint); + + // Pick region from location HEAD request + if (rgn?.Length == 0) + rgn = BucketRegionCache.Instance.Exists(bucketName) + ? await BucketRegionCache.Update(minioClient, bucketName).ConfigureAwait(false) + : BucketRegionCache.Instance.Region(bucketName); + + // Defaults to us-east-1 if region could not be found + return rgn?.Length == 0 ? "us-east-1" : rgn; + } + + /// + /// Delegate errors to handlers + /// + /// + /// + /// + /// + private static void HandleIfErrorResponse(this IMinioClient minioClient, ResponseResult response, + IEnumerable handlers, + DateTime startTime) + { + // Logs Response if HTTP tracing is enabled + if (minioClient.Config.TraceHttp) + { + var now = DateTime.Now; + minioClient.LogRequest(response.Request, response, (now - startTime).TotalMilliseconds); + } + + if (handlers is null) throw new ArgumentNullException(nameof(handlers)); + + // Run through handlers passed to take up error handling + foreach (var handler in handlers) handler.Handle(response); + + // Fall back default error handler + minioClient.DefaultErrorHandler.Handle(response); + } +} diff --git a/LibExternal/Minio/ServiceCollectionExtensions.cs b/LibExternal/Minio/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..7c14bb3 --- /dev/null +++ b/LibExternal/Minio/ServiceCollectionExtensions.cs @@ -0,0 +1,64 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//using Microsoft.Extensions.DependencyInjection; +//using Microsoft.Extensions.DependencyInjection.Extensions; + +//namespace Minio; + +//public static class ServiceCollectionExtensions +//{ +// public static IServiceCollection AddMinio( +// this IServiceCollection services, +// string accessKey, +// string secretKey, +// ServiceLifetime lifetime = ServiceLifetime.Singleton) +// { +// if (services is null) throw new ArgumentNullException(nameof(services)); + +// _ = services.AddMinio(configureClient => configureClient.WithCredentials(accessKey, secretKey), lifetime); +// return services; +// } + +// public static IServiceCollection AddMinio( +// this IServiceCollection services, +// Action configureClient, +// ServiceLifetime lifetime = ServiceLifetime.Singleton) +// { +// if (services is null) throw new ArgumentNullException(nameof(services)); +// if (configureClient == null) throw new ArgumentNullException(nameof(configureClient)); + +// var minioClientFactory = new MinioClientFactory(configureClient); +// services.TryAddSingleton(minioClientFactory); + +// var client = minioClientFactory.CreateClient(); +// client.Config.ServiceProvider = services.BuildServiceProvider(); +// switch (lifetime) +// { +// case ServiceLifetime.Singleton: +// services.TryAddSingleton(_ => client); +// break; +// case ServiceLifetime.Scoped: +// services.TryAddScoped(_ => client); +// break; +// case ServiceLifetime.Transient: +// services.TryAddTransient(_ => client); +// break; +// } + +// return services; +// } +//} diff --git a/LibExternal/Minio/V4Authenticator.cs b/LibExternal/Minio/V4Authenticator.cs new file mode 100644 index 0000000..a96019b --- /dev/null +++ b/LibExternal/Minio/V4Authenticator.cs @@ -0,0 +1,575 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017, 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Globalization; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using Minio.Helper; + +namespace Minio; + +/// +/// V4Authenticator implements IAuthenticator interface. +/// +internal class V4Authenticator +{ + // + // Excerpts from @lsegal - https://github.com/aws/aws-sdk-js/issues/659#issuecomment-120477258 + // + // User-Agent: + // + // This is ignored from signing because signing this causes problems with generating pre-signed URLs + // (that are executed by other agents) or when customers pass requests through proxies, which may + // modify the user-agent. + // + // Authorization: + // + // Is skipped for obvious reasons + // + private static readonly HashSet ignoredHeaders = new(StringComparer.OrdinalIgnoreCase) + { + "authorization", "user-agent" + }; + + private readonly string accessKey; + private readonly string region; + private readonly string secretKey; + private readonly string sessionToken; + + private readonly string sha256EmptyFileHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + + /// + /// Authenticator constructor. + /// + /// + /// Access key id + /// Secret access key + /// Region if specifically set + /// sessionToken + public V4Authenticator(bool secure, string accessKey, string secretKey, string region = "", + string sessionToken = "") + { + IsSecure = secure; + this.accessKey = accessKey; + this.secretKey = secretKey; + IsAnonymous = Utils.IsAnonymousClient(accessKey, secretKey); + this.region = region; + this.sessionToken = sessionToken; + } + + internal bool IsAnonymous { get; } + internal bool IsSecure { get; } + + private string GetRegion(string endpoint) + { + if (!string.IsNullOrEmpty(region)) return region; + + var endpointRegion = RegionHelper.GetRegionFromEndpoint(endpoint); + return string.IsNullOrEmpty(endpointRegion) ? "us-east-1" : endpointRegion; + } + + /// + /// Implements Authenticate interface method for IAuthenticator. + /// + /// Instantiated IRestRequest object + /// boolean; if true role credentials, otherwise IAM user + public string Authenticate(HttpRequestMessageBuilder requestBuilder, bool isSts = false) + { + var signingDate = DateTime.UtcNow; + + SetContentSha256(requestBuilder, isSts); + + requestBuilder.RequestUri = requestBuilder.Request.RequestUri; + var requestUri = requestBuilder.RequestUri; + + if (requestUri.Port is 80 or 443) + SetHostHeader(requestBuilder, requestUri.Host); + else + SetHostHeader(requestBuilder, requestUri.Host + ":" + requestUri.Port); + SetDateHeader(requestBuilder, signingDate); + SetSessionTokenHeader(requestBuilder, sessionToken); + + var headersToSign = GetHeadersToSign(requestBuilder); + var signedHeaders = GetSignedHeaders(headersToSign); + + var canonicalRequest = GetCanonicalRequest(requestBuilder, headersToSign); + ReadOnlySpan canonicalRequestBytes = Encoding.UTF8.GetBytes(canonicalRequest); + var hash = ComputeSha256(canonicalRequestBytes); + var canonicalRequestHash = BytesToHex(hash); + var endpointRegion = GetRegion(requestUri.Host); + var stringToSign = GetStringToSign(endpointRegion, signingDate, canonicalRequestHash, isSts); + var signingKey = GenerateSigningKey(endpointRegion, signingDate, isSts); + ReadOnlySpan stringToSignBytes = Encoding.UTF8.GetBytes(stringToSign); + var signatureBytes = SignHmac(signingKey, stringToSignBytes); + var signature = BytesToHex(signatureBytes); + return GetAuthorizationHeader(signedHeaders, signature, signingDate, endpointRegion, isSts); + } + + /// + /// Get credential string of form {ACCESSID}/date/region/serviceKind/aws4_request. + /// + /// Signature initiated date + /// Region for the credential string + /// boolean; if true role credentials, otherwise IAM user + /// Credential string for the authorization header + public string GetCredentialString(DateTime signingDate, string region, bool isSts = false) + { + var scope = GetScope(region, signingDate, isSts); + return $"{accessKey}/{scope}"; + } + + /// + /// Constructs an authorization header. + /// + /// All signed http headers + /// Hexadecimally encoded computed signature + /// Date for signature to be signed + /// Requested region + /// boolean; if true role credentials, otherwise IAM user + /// Fully formed authorization header + private string GetAuthorizationHeader(string signedHeaders, string signature, DateTime signingDate, string region, + bool isSts = false) + { + var scope = GetScope(region, signingDate, isSts); + return $"AWS4-HMAC-SHA256 Credential={accessKey}/{scope}, SignedHeaders={signedHeaders}, Signature={signature}"; + } + + /// + /// Concatenates sorted list of signed http headers. + /// + /// Sorted dictionary of headers to be signed + /// All signed headers + private string GetSignedHeaders(SortedDictionary headersToSign) + { + return string.Join(";", headersToSign.Keys); + } + + /// + /// Determines and returns the kind of service + /// + /// boolean; if true role credentials, otherwise IAM user + /// returns the kind of service as a string + private string GetService(bool isSts) + { + return isSts ? "sts" : "s3"; + } + + /// + /// Generates signing key based on the region and date. + /// + /// Requested region + /// Date for signature to be signed + /// boolean; if true role credentials, otherwise IAM user + /// bytes of computed hmac + private ReadOnlySpan GenerateSigningKey(string region, DateTime signingDate, bool isSts = false) + { + ReadOnlySpan dateRegionServiceKey; + ReadOnlySpan requestBytes; + + ReadOnlySpan serviceBytes = Encoding.UTF8.GetBytes(GetService(isSts)); + ReadOnlySpan formattedDateBytes = + Encoding.UTF8.GetBytes(signingDate.ToString("yyyyMMdd", CultureInfo.InvariantCulture)); + ReadOnlySpan formattedKeyBytes = Encoding.UTF8.GetBytes($"AWS4{secretKey}"); + var dateKey = SignHmac(formattedKeyBytes, formattedDateBytes); + ReadOnlySpan regionBytes = Encoding.UTF8.GetBytes(region); + var dateRegionKey = SignHmac(dateKey, regionBytes); + dateRegionServiceKey = SignHmac(dateRegionKey, serviceBytes); + requestBytes = Encoding.UTF8.GetBytes("aws4_request"); + + //var hmac = SignHmac(dateRegionServiceKey, requestBytes); + //var signingKey = Encoding.UTF8.GetString(hmac); + return SignHmac(dateRegionServiceKey, requestBytes); + } + + /// + /// Compute hmac of input content with key. + /// + /// Hmac key + /// Bytes to be hmac computed + /// Computed hmac of input content + private ReadOnlySpan SignHmac(ReadOnlySpan key, ReadOnlySpan content) + { +#if NETSTANDARD + using var hmac = new HMACSHA256(key.ToArray()); + hmac.Initialize(); + return hmac.ComputeHash(content.ToArray()); +#else + return HMACSHA256.HashData(key, content); +#endif + } + + /// + /// Get string to sign. + /// + /// Requested region + /// Date for signature to be signed + /// Hexadecimal encoded sha256 checksum of canonicalRequest + /// boolean; if true role credentials, otherwise IAM user + /// String to sign + private string GetStringToSign(string region, DateTime signingDate, + string canonicalRequestHash, bool isSts = false) + { + var scope = GetScope(region, signingDate, isSts); + return $"AWS4-HMAC-SHA256\n{signingDate:yyyyMMddTHHmmssZ}\n{scope}\n{canonicalRequestHash}"; + } + + /// + /// Get scope. + /// + /// Requested region + /// Date for signature to be signed + /// boolean; if true role credentials, otherwise IAM user + /// Scope string + private string GetScope(string region, DateTime signingDate, bool isSts = false) + { + return $"{signingDate:yyyyMMdd}/{region}/{GetService(isSts)}/aws4_request"; + } + + /// + /// Compute sha256 checksum. + /// + /// Bytes body + /// Bytes of sha256 checksum + private ReadOnlySpan ComputeSha256(ReadOnlySpan body) + { +#if NETSTANDARD + using var sha = SHA256.Create(); + ReadOnlySpan hash + = sha.ComputeHash(body.ToArray()); +#else + ReadOnlySpan hash = SHA256.HashData(body); +#endif + return hash; + } + + /// + /// Convert bytes to hexadecimal string. + /// + /// Bytes of any checksum + /// Hexlified string of input bytes + private string BytesToHex(ReadOnlySpan checkSum) + { + return BitConverter.ToString(checkSum.ToArray()).Replace("-", string.Empty, StringComparison.OrdinalIgnoreCase) + .ToLowerInvariant(); + } + + /// + /// Generate signature for post policy. + /// + /// Requested region + /// Date for signature to be signed + /// Base64 encoded policy JSON + /// Computed signature + public string PresignPostSignature(string region, DateTime signingDate, string policyBase64) + { + var signingKey = GenerateSigningKey(region, signingDate); + ReadOnlySpan stringToSignBytes = Encoding.UTF8.GetBytes(policyBase64); + var signatureBytes = SignHmac(signingKey, stringToSignBytes); + var signature = BytesToHex(signatureBytes); + return signature; + } + + /// + /// Presigns any input client object with a requested expiry. + /// + /// Instantiated requestBuilder + /// Expiration in seconds + /// Region of storage + /// Value for session token + /// Optional requestBuilder date and time in UTC + /// Presigned url + internal string PresignURL(HttpRequestMessageBuilder requestBuilder, int expires, string region = "", + string sessionToken = "", DateTime? reqDate = null) + { + var signingDate = reqDate ?? DateTime.UtcNow; + + if (string.IsNullOrWhiteSpace(region)) region = GetRegion(requestBuilder.RequestUri.Host); + + var requestUri = requestBuilder.RequestUri; + var requestQuery = requestUri.Query; + + var headersToSign = GetHeadersToSign(requestBuilder); + if (!string.IsNullOrEmpty(sessionToken)) headersToSign["X-Amz-Security-Token"] = sessionToken; + + if (requestQuery.Length > 0) requestQuery += "&"; + requestQuery += "X-Amz-Algorithm=AWS4-HMAC-SHA256&"; + requestQuery += "X-Amz-Credential=" + + Uri.EscapeDataString(accessKey + "/" + GetScope(region, signingDate)) + + "&"; + requestQuery += "X-Amz-Date=" + + signingDate.ToString("yyyyMMddTHHmmssZ", CultureInfo.InvariantCulture) + + "&"; + requestQuery += "X-Amz-Expires=" + + expires + + "&"; + requestQuery += "X-Amz-SignedHeaders=host"; + + var presignUri = new UriBuilder(requestUri) { Query = requestQuery }.Uri; + var canonicalRequest = GetPresignCanonicalRequest(requestBuilder.Method, presignUri, headersToSign); + var headers = string.Concat(headersToSign.Select(p => $"&{p.Key}={Utils.UrlEncode(p.Value)}")); + ReadOnlySpan canonicalRequestBytes = Encoding.UTF8.GetBytes(canonicalRequest); + var canonicalRequestHash = BytesToHex(ComputeSha256(canonicalRequestBytes)); + var stringToSign = GetStringToSign(region, signingDate, canonicalRequestHash); + var signingKey = GenerateSigningKey(region, signingDate); + ReadOnlySpan stringToSignBytes = Encoding.UTF8.GetBytes(stringToSign); + var signatureBytes = SignHmac(signingKey, stringToSignBytes); + var signature = BytesToHex(signatureBytes); + + // Return presigned url. + var signedUri = new UriBuilder(presignUri) { Query = $"{requestQuery}{headers}&X-Amz-Signature={signature}" }; + if (signedUri.Uri.IsDefaultPort) signedUri.Port = -1; + return Convert.ToString(signedUri, CultureInfo.InvariantCulture); + } + + /// + /// Get presign canonical requestBuilder. + /// + /// HTTP method used for this requestBuilder + /// + /// Full url for this requestBuilder, including all query parameters except for headers and + /// X-Amz-Signature + /// + /// The key-value of headers. + /// Presigned canonical requestBuilder + internal string GetPresignCanonicalRequest(HttpMethod requestMethod, Uri uri, + SortedDictionary headersToSign) + { + var canonicalStringList = new LinkedList(); + _ = canonicalStringList.AddLast(requestMethod.ToString()); + + var path = uri.AbsolutePath; + + _ = canonicalStringList.AddLast(path); + var queryParams = uri.Query.TrimStart('?').Split('&').ToList(); + queryParams.AddRange(headersToSign.Select(cv => + $"{Utils.UrlEncode(cv.Key)}={Utils.UrlEncode(cv.Value.Trim())}")); + queryParams.Sort(StringComparer.Ordinal); + var query = string.Join("&", queryParams); + _ = canonicalStringList.AddLast(query); + var canonicalHost = GetCanonicalHost(uri); + _ = canonicalStringList.AddLast($"host:{canonicalHost}"); + + _ = canonicalStringList.AddLast(string.Empty); + _ = canonicalStringList.AddLast("host"); + _ = canonicalStringList.AddLast("UNSIGNED-PAYLOAD"); + + return string.Join("\n", canonicalStringList); + } + + private static string GetCanonicalHost(Uri url) + { + if (url.Port is > 0 and not 80 and not 443) + return $"{url.Host}:{url.Port}"; + return url.Host; + } + + /// + /// Get canonical requestBuilder. + /// + /// Instantiated requestBuilder object + /// Dictionary of http headers to be signed + /// Canonical Request + private string GetCanonicalRequest(HttpRequestMessageBuilder requestBuilder, + SortedDictionary headersToSign) + { + var canonicalStringList = new LinkedList(); + // METHOD + _ = canonicalStringList.AddLast(requestBuilder.Method.ToString()); + + var queryParamsDict = new Dictionary(StringComparer.Ordinal); + if (requestBuilder.QueryParameters is not null) + foreach (var kvp in requestBuilder.QueryParameters) + queryParamsDict[kvp.Key] = Uri.EscapeDataString(kvp.Value); + + var queryParams = ""; + if (queryParamsDict.Count > 0) + { + var sb1 = new StringBuilder(); + var queryKeys = new List(queryParamsDict.Keys); + queryKeys.Sort(StringComparer.Ordinal); + foreach (var p in queryKeys) + { + if (sb1.Length > 0) + _ = sb1.Append('&'); + _ = sb1.AppendFormat(CultureInfo.InvariantCulture, "{0}={1}", p, queryParamsDict[p]); + } + + queryParams = sb1.ToString(); + } + + var isFormData = false; + if (requestBuilder.Request.Content?.Headers?.ContentType is not null) + isFormData = string.Equals(requestBuilder.Request.Content.Headers.ContentType.ToString(), + "application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase); + + if (string.IsNullOrEmpty(queryParams) && isFormData) + { + // Convert stream content to byte[] + var cntntByteData = Span.Empty; + if (requestBuilder.Request.Content is not null) + cntntByteData = requestBuilder.Request.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult(); + + // UTF conversion - String from bytes + queryParams = Encoding.UTF8.GetString(cntntByteData); + } + + if (!string.IsNullOrEmpty(queryParams) && + !isFormData && + !string.Equals(requestBuilder.RequestUri.Query, "?location=", StringComparison.OrdinalIgnoreCase)) + requestBuilder.RequestUri = new Uri(requestBuilder.RequestUri + "?" + queryParams); + + _ = canonicalStringList.AddLast(requestBuilder.RequestUri.AbsolutePath); + _ = canonicalStringList.AddLast(queryParams); + + // Headers to sign + foreach (var header in headersToSign.Keys) + _ = canonicalStringList.AddLast(header + ":" + S3utils.TrimAll(headersToSign[header])); + _ = canonicalStringList.AddLast(string.Empty); + _ = canonicalStringList.AddLast(string.Join(";", headersToSign.Keys)); + if (headersToSign.TryGetValue("x-amz-content-sha256", out var value)) + _ = canonicalStringList.AddLast(value); + else + _ = canonicalStringList.AddLast(sha256EmptyFileHash); + return string.Join("\n", canonicalStringList); + } + + public static IDictionary ToDictionary(object obj) + { + var json = JsonSerializer.Serialize(obj); + var dictionary = JsonSerializer.Deserialize>(json); + return dictionary; + } + + /// + /// Get headers to be signed. + /// + /// Instantiated requesst + /// Sorted dictionary of headers to be signed + private SortedDictionary GetHeadersToSign(HttpRequestMessageBuilder requestBuilder) + { + var headers = requestBuilder.HeaderParameters.ToList(); + var sortedHeaders = new SortedDictionary(StringComparer.Ordinal); + + foreach (var header in headers) + { + var headerName = header.Key.ToLowerInvariant(); + var headerValue = header.Value; + + if (!ignoredHeaders.Contains(headerName)) sortedHeaders.Add(headerName, headerValue); + } + + return sortedHeaders; + } + + /// + /// Sets 'x-amz-date' http header. + /// + /// Instantiated requestBuilder object + /// Date for signature to be signed + private void SetDateHeader(HttpRequestMessageBuilder requestBuilder, DateTime signingDate) + { + requestBuilder.AddOrUpdateHeaderParameter("x-amz-date", + signingDate.ToString("yyyyMMddTHHmmssZ", CultureInfo.InvariantCulture)); + } + + /// + /// Set 'Host' http header. + /// + /// Instantiated requestBuilder object + /// Host url + private void SetHostHeader(HttpRequestMessageBuilder requestBuilder, string hostUrl) + { + requestBuilder.AddOrUpdateHeaderParameter("Host", hostUrl); + } + + /// + /// Set 'X-Amz-Security-Token' http header. + /// + /// Instantiated requestBuilder object + /// session token + private void SetSessionTokenHeader(HttpRequestMessageBuilder requestBuilder, string sessionToken) + { + if (!string.IsNullOrEmpty(sessionToken)) + requestBuilder.AddOrUpdateHeaderParameter("X-Amz-Security-Token", sessionToken); + } + + /// + /// Set 'x-amz-content-sha256' http header. + /// + /// Instantiated requestBuilder object + /// boolean; if true role credentials, otherwise IAM user + private void SetContentSha256(HttpRequestMessageBuilder requestBuilder, bool isSts = false) + { + if (IsAnonymous) + return; + // No need to compute SHA256 if the endpoint scheme is https + // or the command method is not a Post to delete multiple files + var isMultiDeleteRequest = false; + if (requestBuilder.Method == HttpMethod.Post) + isMultiDeleteRequest = + requestBuilder.QueryParameters.Any(p => p.Key.Equals("delete", StringComparison.OrdinalIgnoreCase)); + + if ((IsSecure && !isSts) || isMultiDeleteRequest) + { + requestBuilder.AddOrUpdateHeaderParameter("x-amz-content-sha256", "UNSIGNED-PAYLOAD"); + return; + } + + // For insecure, authenticated requests set sha256 header instead of MD5. + if (requestBuilder.Method.Equals(HttpMethod.Put) || + requestBuilder.Method.Equals(HttpMethod.Post)) + { + var body = requestBuilder.Content; + if (body.IsEmpty) + { + requestBuilder.AddOrUpdateHeaderParameter("x-amz-content-sha256", sha256EmptyFileHash); + return; + } +#if NETSTANDARD + using var sha = SHA256.Create(); + var hash + = sha.ComputeHash(body.ToArray()); +#else + var hash = SHA256.HashData(body.Span); +#endif + var hex = BitConverter.ToString(hash).Replace("-", string.Empty, StringComparison.OrdinalIgnoreCase) + .ToLowerInvariant(); + requestBuilder.AddOrUpdateHeaderParameter("x-amz-content-sha256", hex); + } + else if (!IsSecure && !requestBuilder.Content.IsEmpty) + { + ReadOnlySpan bytes = Encoding.UTF8.GetBytes(requestBuilder.Content.ToString()); + +#if NETSTANDARD +#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms + using var md5 + = MD5.Create(); +#pragma warning restore CA5351 // Do Not Use Broken Cryptographic Algorithms + var hash + = md5.ComputeHash(bytes.ToArray()); +#else + ReadOnlySpan hash = MD5.HashData(bytes); +#endif + var base64 = Convert.ToBase64String(hash); + requestBuilder.AddHeaderParameter("Content-Md5", base64); + } + else + { + requestBuilder.AddOrUpdateHeaderParameter("x-amz-content-sha256", sha256EmptyFileHash); + } + } +} diff --git a/LibExternal/Minio/bin/Debug/net8.0/Minio.deps.json b/LibExternal/Minio/bin/Debug/net8.0/Minio.deps.json new file mode 100644 index 0000000..defc235 --- /dev/null +++ b/LibExternal/Minio/bin/Debug/net8.0/Minio.deps.json @@ -0,0 +1,53 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v8.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v8.0": { + "Minio/1.0.0": { + "dependencies": { + "System.IO.Hashing": "1.0.0", + "System.Reactive": "1.0.0" + }, + "runtime": { + "Minio.dll": {} + } + }, + "System.IO.Hashing/1.0.0": { + "runtime": { + "System.IO.Hashing.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + }, + "System.Reactive/1.0.0": { + "runtime": { + "System.Reactive.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + } + } + }, + "libraries": { + "Minio/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "System.IO.Hashing/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "System.Reactive/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/LibExternal/Minio/bin/Debug/net8.0/Minio.dll b/LibExternal/Minio/bin/Debug/net8.0/Minio.dll new file mode 100644 index 0000000..ca876c9 Binary files /dev/null and b/LibExternal/Minio/bin/Debug/net8.0/Minio.dll differ diff --git a/LibExternal/Minio/bin/Debug/net8.0/Minio.pdb b/LibExternal/Minio/bin/Debug/net8.0/Minio.pdb new file mode 100644 index 0000000..a76fbf4 Binary files /dev/null and b/LibExternal/Minio/bin/Debug/net8.0/Minio.pdb differ diff --git a/LibExternal/Minio/bin/Debug/net8.0/System.IO.Hashing.dll b/LibExternal/Minio/bin/Debug/net8.0/System.IO.Hashing.dll new file mode 100644 index 0000000..2888d97 Binary files /dev/null and b/LibExternal/Minio/bin/Debug/net8.0/System.IO.Hashing.dll differ diff --git a/LibExternal/Minio/bin/Debug/net8.0/System.IO.Hashing.pdb b/LibExternal/Minio/bin/Debug/net8.0/System.IO.Hashing.pdb new file mode 100644 index 0000000..1342663 Binary files /dev/null and b/LibExternal/Minio/bin/Debug/net8.0/System.IO.Hashing.pdb differ diff --git a/LibExternal/Minio/bin/Debug/net8.0/System.Reactive.dll b/LibExternal/Minio/bin/Debug/net8.0/System.Reactive.dll new file mode 100644 index 0000000..7b8ccea Binary files /dev/null and b/LibExternal/Minio/bin/Debug/net8.0/System.Reactive.dll differ diff --git a/LibExternal/Minio/bin/Debug/net8.0/System.Reactive.pdb b/LibExternal/Minio/bin/Debug/net8.0/System.Reactive.pdb new file mode 100644 index 0000000..7349916 Binary files /dev/null and b/LibExternal/Minio/bin/Debug/net8.0/System.Reactive.pdb differ diff --git a/LibExternal/Minio/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs b/LibExternal/Minio/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs new file mode 100644 index 0000000..2217181 --- /dev/null +++ b/LibExternal/Minio/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")] diff --git a/LibExternal/Minio/obj/Debug/net8.0/Minio.AssemblyInfo.cs b/LibExternal/Minio/obj/Debug/net8.0/Minio.AssemblyInfo.cs new file mode 100644 index 0000000..5fdea9f --- /dev/null +++ b/LibExternal/Minio/obj/Debug/net8.0/Minio.AssemblyInfo.cs @@ -0,0 +1,23 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("Minio")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+37e709e30d2205dd559831f65211b8dd5d40a303")] +[assembly: System.Reflection.AssemblyProductAttribute("Minio")] +[assembly: System.Reflection.AssemblyTitleAttribute("Minio")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/LibExternal/Minio/obj/Debug/net8.0/Minio.AssemblyInfoInputs.cache b/LibExternal/Minio/obj/Debug/net8.0/Minio.AssemblyInfoInputs.cache new file mode 100644 index 0000000..a4fb9db --- /dev/null +++ b/LibExternal/Minio/obj/Debug/net8.0/Minio.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +6d68bfa2b51223e2d54a6ed5f2eedfe0a0be19a66768d4a0c052beed5dc92b03 diff --git a/LibExternal/Minio/obj/Debug/net8.0/Minio.GeneratedMSBuildEditorConfig.editorconfig b/LibExternal/Minio/obj/Debug/net8.0/Minio.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..8519326 --- /dev/null +++ b/LibExternal/Minio/obj/Debug/net8.0/Minio.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,15 @@ +is_global = true +build_property.TargetFramework = net8.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = Minio +build_property.ProjectDir = C:\KIT\Kit.Core\LibExternal\Minio\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 8.0 +build_property.EnableCodeStyleSeverity = diff --git a/LibExternal/Minio/obj/Debug/net8.0/Minio.GlobalUsings.g.cs b/LibExternal/Minio/obj/Debug/net8.0/Minio.GlobalUsings.g.cs new file mode 100644 index 0000000..8578f3d --- /dev/null +++ b/LibExternal/Minio/obj/Debug/net8.0/Minio.GlobalUsings.g.cs @@ -0,0 +1,8 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/LibExternal/Minio/obj/Debug/net8.0/Minio.assets.cache b/LibExternal/Minio/obj/Debug/net8.0/Minio.assets.cache new file mode 100644 index 0000000..75eca1b Binary files /dev/null and b/LibExternal/Minio/obj/Debug/net8.0/Minio.assets.cache differ diff --git a/LibExternal/Minio/obj/Debug/net8.0/Minio.csproj.AssemblyReference.cache b/LibExternal/Minio/obj/Debug/net8.0/Minio.csproj.AssemblyReference.cache new file mode 100644 index 0000000..6060501 Binary files /dev/null and b/LibExternal/Minio/obj/Debug/net8.0/Minio.csproj.AssemblyReference.cache differ diff --git a/LibExternal/Minio/obj/Debug/net8.0/Minio.csproj.CoreCompileInputs.cache b/LibExternal/Minio/obj/Debug/net8.0/Minio.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..3d4e014 --- /dev/null +++ b/LibExternal/Minio/obj/Debug/net8.0/Minio.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +ef144992da231a130b4a5fac783dfa20d5242be2f59a657983e3f78d125af790 diff --git a/LibExternal/Minio/obj/Debug/net8.0/Minio.csproj.FileListAbsolute.txt b/LibExternal/Minio/obj/Debug/net8.0/Minio.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..0b9f152 --- /dev/null +++ b/LibExternal/Minio/obj/Debug/net8.0/Minio.csproj.FileListAbsolute.txt @@ -0,0 +1,17 @@ +C:\KIT\Kit.Core\LibExternal\Minio\bin\Debug\net8.0\Minio.deps.json +C:\KIT\Kit.Core\LibExternal\Minio\bin\Debug\net8.0\Minio.dll +C:\KIT\Kit.Core\LibExternal\Minio\bin\Debug\net8.0\Minio.pdb +C:\KIT\Kit.Core\LibExternal\Minio\bin\Debug\net8.0\System.IO.Hashing.dll +C:\KIT\Kit.Core\LibExternal\Minio\bin\Debug\net8.0\System.Reactive.dll +C:\KIT\Kit.Core\LibExternal\Minio\bin\Debug\net8.0\System.IO.Hashing.pdb +C:\KIT\Kit.Core\LibExternal\Minio\bin\Debug\net8.0\System.Reactive.pdb +C:\KIT\Kit.Core\LibExternal\Minio\obj\Debug\net8.0\Minio.csproj.AssemblyReference.cache +C:\KIT\Kit.Core\LibExternal\Minio\obj\Debug\net8.0\Minio.GeneratedMSBuildEditorConfig.editorconfig +C:\KIT\Kit.Core\LibExternal\Minio\obj\Debug\net8.0\Minio.AssemblyInfoInputs.cache +C:\KIT\Kit.Core\LibExternal\Minio\obj\Debug\net8.0\Minio.AssemblyInfo.cs +C:\KIT\Kit.Core\LibExternal\Minio\obj\Debug\net8.0\Minio.csproj.CoreCompileInputs.cache +C:\KIT\Kit.Core\LibExternal\Minio\obj\Debug\net8.0\Minio.csproj.Up2Date +C:\KIT\Kit.Core\LibExternal\Minio\obj\Debug\net8.0\Minio.dll +C:\KIT\Kit.Core\LibExternal\Minio\obj\Debug\net8.0\refint\Minio.dll +C:\KIT\Kit.Core\LibExternal\Minio\obj\Debug\net8.0\Minio.pdb +C:\KIT\Kit.Core\LibExternal\Minio\obj\Debug\net8.0\ref\Minio.dll diff --git a/LibExternal/Minio/obj/Debug/net8.0/Minio.csproj.Up2Date b/LibExternal/Minio/obj/Debug/net8.0/Minio.csproj.Up2Date new file mode 100644 index 0000000..e69de29 diff --git a/LibExternal/Minio/obj/Debug/net8.0/Minio.dll b/LibExternal/Minio/obj/Debug/net8.0/Minio.dll new file mode 100644 index 0000000..ca876c9 Binary files /dev/null and b/LibExternal/Minio/obj/Debug/net8.0/Minio.dll differ diff --git a/LibExternal/Minio/obj/Debug/net8.0/Minio.pdb b/LibExternal/Minio/obj/Debug/net8.0/Minio.pdb new file mode 100644 index 0000000..a76fbf4 Binary files /dev/null and b/LibExternal/Minio/obj/Debug/net8.0/Minio.pdb differ diff --git a/LibExternal/Minio/obj/Debug/net8.0/ref/Minio.dll b/LibExternal/Minio/obj/Debug/net8.0/ref/Minio.dll new file mode 100644 index 0000000..bfd3cc5 Binary files /dev/null and b/LibExternal/Minio/obj/Debug/net8.0/ref/Minio.dll differ diff --git a/LibExternal/Minio/obj/Debug/net8.0/refint/Minio.dll b/LibExternal/Minio/obj/Debug/net8.0/refint/Minio.dll new file mode 100644 index 0000000..bfd3cc5 Binary files /dev/null and b/LibExternal/Minio/obj/Debug/net8.0/refint/Minio.dll differ diff --git a/LibExternal/Minio/obj/Minio.csproj.nuget.dgspec.json b/LibExternal/Minio/obj/Minio.csproj.nuget.dgspec.json new file mode 100644 index 0000000..9c0f03e --- /dev/null +++ b/LibExternal/Minio/obj/Minio.csproj.nuget.dgspec.json @@ -0,0 +1,213 @@ +{ + "format": 1, + "restore": { + "C:\\KIT\\Kit.Core\\LibExternal\\Minio\\Minio.csproj": {} + }, + "projects": { + "C:\\KIT\\Kit.Core\\LibExternal\\Minio\\Minio.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\KIT\\Kit.Core\\LibExternal\\Minio\\Minio.csproj", + "projectName": "Minio", + "projectPath": "C:\\KIT\\Kit.Core\\LibExternal\\Minio\\Minio.csproj", + "packagesPath": "C:\\Users\\user\\.nuget\\packages\\", + "outputPath": "C:\\KIT\\Kit.Core\\LibExternal\\Minio\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\user\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "C:\\GIT\\RiskProf.Modules.Core\\RiskProf.LK.Back\\RiskProf.LK.Back\\packages": {}, + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": { + "C:\\KIT\\Kit.Core\\LibExternal\\System.IO.Hashing\\System.IO.Hashing.csproj": { + "projectPath": "C:\\KIT\\Kit.Core\\LibExternal\\System.IO.Hashing\\System.IO.Hashing.csproj" + }, + "C:\\KIT\\Kit.Core\\LibExternal\\System.Reactive\\System.Reactive.csproj": { + "projectPath": "C:\\KIT\\Kit.Core\\LibExternal\\System.Reactive\\System.Reactive.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.302/PortableRuntimeIdentifierGraph.json" + } + } + }, + "C:\\KIT\\Kit.Core\\LibExternal\\System.IO.Hashing\\System.IO.Hashing.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\KIT\\Kit.Core\\LibExternal\\System.IO.Hashing\\System.IO.Hashing.csproj", + "projectName": "System.IO.Hashing", + "projectPath": "C:\\KIT\\Kit.Core\\LibExternal\\System.IO.Hashing\\System.IO.Hashing.csproj", + "packagesPath": "C:\\Users\\user\\.nuget\\packages\\", + "outputPath": "C:\\KIT\\Kit.Core\\LibExternal\\System.IO.Hashing\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\user\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "C:\\GIT\\RiskProf.Modules.Core\\RiskProf.LK.Back\\RiskProf.LK.Back\\packages": {}, + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.302/PortableRuntimeIdentifierGraph.json" + } + } + }, + "C:\\KIT\\Kit.Core\\LibExternal\\System.Reactive\\System.Reactive.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\KIT\\Kit.Core\\LibExternal\\System.Reactive\\System.Reactive.csproj", + "projectName": "System.Reactive", + "projectPath": "C:\\KIT\\Kit.Core\\LibExternal\\System.Reactive\\System.Reactive.csproj", + "packagesPath": "C:\\Users\\user\\.nuget\\packages\\", + "outputPath": "C:\\KIT\\Kit.Core\\LibExternal\\System.Reactive\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\user\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "C:\\GIT\\RiskProf.Modules.Core\\RiskProf.LK.Back\\RiskProf.LK.Back\\packages": {}, + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.302/PortableRuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/LibExternal/Minio/obj/Minio.csproj.nuget.g.props b/LibExternal/Minio/obj/Minio.csproj.nuget.g.props new file mode 100644 index 0000000..020441f --- /dev/null +++ b/LibExternal/Minio/obj/Minio.csproj.nuget.g.props @@ -0,0 +1,16 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\user\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages + PackageReference + 6.14.0 + + + + + + \ No newline at end of file diff --git a/LibExternal/Minio/obj/Minio.csproj.nuget.g.targets b/LibExternal/Minio/obj/Minio.csproj.nuget.g.targets new file mode 100644 index 0000000..3dc06ef --- /dev/null +++ b/LibExternal/Minio/obj/Minio.csproj.nuget.g.targets @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/LibExternal/Minio/obj/project.assets.json b/LibExternal/Minio/obj/project.assets.json new file mode 100644 index 0000000..af89f30 --- /dev/null +++ b/LibExternal/Minio/obj/project.assets.json @@ -0,0 +1,122 @@ +{ + "version": 3, + "targets": { + "net8.0": { + "System.IO.Hashing/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v8.0", + "compile": { + "bin/placeholder/System.IO.Hashing.dll": {} + }, + "runtime": { + "bin/placeholder/System.IO.Hashing.dll": {} + } + }, + "System.Reactive/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v8.0", + "compile": { + "bin/placeholder/System.Reactive.dll": {} + }, + "runtime": { + "bin/placeholder/System.Reactive.dll": {} + } + } + } + }, + "libraries": { + "System.IO.Hashing/1.0.0": { + "type": "project", + "path": "../System.IO.Hashing/System.IO.Hashing.csproj", + "msbuildProject": "../System.IO.Hashing/System.IO.Hashing.csproj" + }, + "System.Reactive/1.0.0": { + "type": "project", + "path": "../System.Reactive/System.Reactive.csproj", + "msbuildProject": "../System.Reactive/System.Reactive.csproj" + } + }, + "projectFileDependencyGroups": { + "net8.0": [ + "System.IO.Hashing >= 1.0.0", + "System.Reactive >= 1.0.0" + ] + }, + "packageFolders": { + "C:\\Users\\user\\.nuget\\packages\\": {}, + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\KIT\\Kit.Core\\LibExternal\\Minio\\Minio.csproj", + "projectName": "Minio", + "projectPath": "C:\\KIT\\Kit.Core\\LibExternal\\Minio\\Minio.csproj", + "packagesPath": "C:\\Users\\user\\.nuget\\packages\\", + "outputPath": "C:\\KIT\\Kit.Core\\LibExternal\\Minio\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\user\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "C:\\GIT\\RiskProf.Modules.Core\\RiskProf.LK.Back\\RiskProf.LK.Back\\packages": {}, + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": { + "C:\\KIT\\Kit.Core\\LibExternal\\System.IO.Hashing\\System.IO.Hashing.csproj": { + "projectPath": "C:\\KIT\\Kit.Core\\LibExternal\\System.IO.Hashing\\System.IO.Hashing.csproj" + }, + "C:\\KIT\\Kit.Core\\LibExternal\\System.Reactive\\System.Reactive.csproj": { + "projectPath": "C:\\KIT\\Kit.Core\\LibExternal\\System.Reactive\\System.Reactive.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.302/PortableRuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/LibExternal/Minio/obj/project.nuget.cache b/LibExternal/Minio/obj/project.nuget.cache new file mode 100644 index 0000000..ab32145 --- /dev/null +++ b/LibExternal/Minio/obj/project.nuget.cache @@ -0,0 +1,8 @@ +{ + "version": 2, + "dgSpecHash": "IAdzd638C5s=", + "success": true, + "projectFilePath": "C:\\KIT\\Kit.Core\\LibExternal\\Minio\\Minio.csproj", + "expectedPackageFiles": [], + "logs": [] +} \ No newline at end of file diff --git a/LibExternal/Npgsql/BackendMessages/AuthenticationMessages.cs b/LibExternal/Npgsql/BackendMessages/AuthenticationMessages.cs new file mode 100644 index 0000000..5c7222e --- /dev/null +++ b/LibExternal/Npgsql/BackendMessages/AuthenticationMessages.cs @@ -0,0 +1,234 @@ +using System; +using System.Collections.Generic; +using Npgsql.Internal; +using Npgsql.Logging; +using Npgsql.Util; + +namespace Npgsql.BackendMessages; + +abstract class AuthenticationRequestMessage : IBackendMessage +{ + public BackendMessageCode Code => BackendMessageCode.AuthenticationRequest; + internal abstract AuthenticationRequestType AuthRequestType { get; } +} + +class AuthenticationOkMessage : AuthenticationRequestMessage +{ + internal override AuthenticationRequestType AuthRequestType => AuthenticationRequestType.AuthenticationOk; + + internal static readonly AuthenticationOkMessage Instance = new(); + AuthenticationOkMessage() { } +} + +class AuthenticationKerberosV5Message : AuthenticationRequestMessage +{ + internal override AuthenticationRequestType AuthRequestType => AuthenticationRequestType.AuthenticationKerberosV5; + + internal static readonly AuthenticationKerberosV5Message Instance = new(); + AuthenticationKerberosV5Message() { } +} + +class AuthenticationCleartextPasswordMessage : AuthenticationRequestMessage +{ + internal override AuthenticationRequestType AuthRequestType => AuthenticationRequestType.AuthenticationCleartextPassword; + + internal static readonly AuthenticationCleartextPasswordMessage Instance = new(); + AuthenticationCleartextPasswordMessage() { } +} + +class AuthenticationMD5PasswordMessage : AuthenticationRequestMessage +{ + internal override AuthenticationRequestType AuthRequestType => AuthenticationRequestType.AuthenticationMD5Password; + + internal byte[] Salt { get; private set; } + + internal static AuthenticationMD5PasswordMessage Load(NpgsqlReadBuffer buf) + { + var salt = new byte[4]; + buf.ReadBytes(salt, 0, 4); + return new AuthenticationMD5PasswordMessage(salt); + } + + AuthenticationMD5PasswordMessage(byte[] salt) + { + Salt = salt; + } +} + +class AuthenticationSCMCredentialMessage : AuthenticationRequestMessage +{ + internal override AuthenticationRequestType AuthRequestType => AuthenticationRequestType.AuthenticationSCMCredential; + + internal static readonly AuthenticationSCMCredentialMessage Instance = new(); + AuthenticationSCMCredentialMessage() { } +} + +class AuthenticationGSSMessage : AuthenticationRequestMessage +{ + internal override AuthenticationRequestType AuthRequestType => AuthenticationRequestType.AuthenticationGSS; + + internal static readonly AuthenticationGSSMessage Instance = new(); + AuthenticationGSSMessage() { } +} + +class AuthenticationGSSContinueMessage : AuthenticationRequestMessage +{ + internal override AuthenticationRequestType AuthRequestType => AuthenticationRequestType.AuthenticationGSSContinue; + + internal byte[] AuthenticationData { get; private set; } + + internal static AuthenticationGSSContinueMessage Load(NpgsqlReadBuffer buf, int len) + { + len -= 4; // The AuthRequestType code + var authenticationData = new byte[len]; + buf.ReadBytes(authenticationData, 0, len); + return new AuthenticationGSSContinueMessage(authenticationData); + } + + AuthenticationGSSContinueMessage(byte[] authenticationData) + { + AuthenticationData = authenticationData; + } +} + +class AuthenticationSSPIMessage : AuthenticationRequestMessage +{ + internal override AuthenticationRequestType AuthRequestType => AuthenticationRequestType.AuthenticationSSPI; + + internal static readonly AuthenticationSSPIMessage Instance = new(); + AuthenticationSSPIMessage() { } +} + +#region SASL + +class AuthenticationSASLMessage : AuthenticationRequestMessage +{ + internal override AuthenticationRequestType AuthRequestType => AuthenticationRequestType.AuthenticationSASL; + internal List Mechanisms { get; } = new(); + + internal AuthenticationSASLMessage(NpgsqlReadBuffer buf) + { + while (buf.Buffer[buf.ReadPosition] != 0) + Mechanisms.Add(buf.ReadNullTerminatedString()); + buf.ReadByte(); + if (Mechanisms.Count == 0) + throw new NpgsqlException("Received AuthenticationSASL message with 0 mechanisms!"); + } +} + +class AuthenticationSASLContinueMessage : AuthenticationRequestMessage +{ + internal override AuthenticationRequestType AuthRequestType => AuthenticationRequestType.AuthenticationSASLContinue; + internal byte[] Payload { get; } + + internal AuthenticationSASLContinueMessage(NpgsqlReadBuffer buf, int len) + { + Payload = new byte[len]; + buf.ReadBytes(Payload, 0, len); + } +} + +class AuthenticationSCRAMServerFirstMessage +{ + static readonly NpgsqlLogger Log = NpgsqlLogManager.CreateLogger(nameof(AuthenticationSCRAMServerFirstMessage)); + + internal string Nonce { get; } + internal string Salt { get; } + internal int Iteration { get; } + + internal static AuthenticationSCRAMServerFirstMessage Load(byte[] bytes) + { + var data = PGUtil.UTF8Encoding.GetString(bytes); + string? nonce = null, salt = null; + var iteration = -1; + + foreach (var part in data.Split(',')) + { + if (part.StartsWith("r=", StringComparison.Ordinal)) + nonce = part.Substring(2); + else if (part.StartsWith("s=", StringComparison.Ordinal)) + salt = part.Substring(2); + else if (part.StartsWith("i=", StringComparison.Ordinal)) + iteration = int.Parse(part.Substring(2)); + else + Log.Debug("Unknown part in SCRAM server-first message:" + part); + } + + if (nonce == null) + throw new NpgsqlException("Server nonce not received in SCRAM server-first message"); + if (salt == null) + throw new NpgsqlException("Server salt not received in SCRAM server-first message"); + if (iteration == -1) + throw new NpgsqlException("Server iterations not received in SCRAM server-first message"); + + return new AuthenticationSCRAMServerFirstMessage(nonce, salt, iteration); + } + + AuthenticationSCRAMServerFirstMessage(string nonce, string salt, int iteration) + { + Nonce = nonce; + Salt = salt; + Iteration = iteration; + } +} + +class AuthenticationSASLFinalMessage : AuthenticationRequestMessage +{ + internal override AuthenticationRequestType AuthRequestType => AuthenticationRequestType.AuthenticationSASLFinal; + internal byte[] Payload { get; } + + internal AuthenticationSASLFinalMessage(NpgsqlReadBuffer buf, int len) + { + Payload = new byte[len]; + buf.ReadBytes(Payload, 0, len); + } +} + +class AuthenticationSCRAMServerFinalMessage +{ + static readonly NpgsqlLogger Log = NpgsqlLogManager.CreateLogger(nameof(AuthenticationSCRAMServerFinalMessage)); + + internal string ServerSignature { get; } + + internal static AuthenticationSCRAMServerFinalMessage Load(byte[] bytes) + { + var data = PGUtil.UTF8Encoding.GetString(bytes); + string? serverSignature = null; + + foreach (var part in data.Split(',')) + { + if (part.StartsWith("v=", StringComparison.Ordinal)) + serverSignature = part.Substring(2); + else + Log.Debug("Unknown part in SCRAM server-first message:" + part); + } + + if (serverSignature == null) + throw new NpgsqlException("Server signature not received in SCRAM server-final message"); + + return new AuthenticationSCRAMServerFinalMessage(serverSignature); + } + + internal AuthenticationSCRAMServerFinalMessage(string serverSignature) + => ServerSignature = serverSignature; +} + +#endregion SASL + +// TODO: Remove Authentication prefix from everything +enum AuthenticationRequestType +{ + AuthenticationOk = 0, + AuthenticationKerberosV4 = 1, + AuthenticationKerberosV5 = 2, + AuthenticationCleartextPassword = 3, + AuthenticationCryptPassword = 4, + AuthenticationMD5Password = 5, + AuthenticationSCMCredential = 6, + AuthenticationGSS = 7, + AuthenticationGSSContinue = 8, + AuthenticationSSPI = 9, + AuthenticationSASL = 10, + AuthenticationSASLContinue = 11, + AuthenticationSASLFinal = 12 +} \ No newline at end of file diff --git a/LibExternal/Npgsql/BackendMessages/BackendKeyDataMessage.cs b/LibExternal/Npgsql/BackendMessages/BackendKeyDataMessage.cs new file mode 100644 index 0000000..77be7c9 --- /dev/null +++ b/LibExternal/Npgsql/BackendMessages/BackendKeyDataMessage.cs @@ -0,0 +1,17 @@ +using Npgsql.Internal; + +namespace Npgsql.BackendMessages; + +class BackendKeyDataMessage : IBackendMessage +{ + public BackendMessageCode Code => BackendMessageCode.BackendKeyData; + + internal int BackendProcessId { get; private set; } + internal int BackendSecretKey { get; private set; } + + internal BackendKeyDataMessage(NpgsqlReadBuffer buf) + { + BackendProcessId = buf.ReadInt32(); + BackendSecretKey = buf.ReadInt32(); + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/BackendMessages/BindCompleteMessage.cs b/LibExternal/Npgsql/BackendMessages/BindCompleteMessage.cs new file mode 100644 index 0000000..bb467f9 --- /dev/null +++ b/LibExternal/Npgsql/BackendMessages/BindCompleteMessage.cs @@ -0,0 +1,8 @@ +namespace Npgsql.BackendMessages; + +class BindCompleteMessage : IBackendMessage +{ + public BackendMessageCode Code => BackendMessageCode.BindComplete; + internal static readonly BindCompleteMessage Instance = new(); + BindCompleteMessage() { } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/BackendMessages/CloseCompletedMessage.cs b/LibExternal/Npgsql/BackendMessages/CloseCompletedMessage.cs new file mode 100644 index 0000000..4e34982 --- /dev/null +++ b/LibExternal/Npgsql/BackendMessages/CloseCompletedMessage.cs @@ -0,0 +1,8 @@ +namespace Npgsql.BackendMessages; + +class CloseCompletedMessage : IBackendMessage +{ + public BackendMessageCode Code => BackendMessageCode.CloseComplete; + internal static readonly CloseCompletedMessage Instance = new(); + CloseCompletedMessage() { } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/BackendMessages/CommandCompleteMessage.cs b/LibExternal/Npgsql/BackendMessages/CommandCompleteMessage.cs new file mode 100644 index 0000000..06f9bd5 --- /dev/null +++ b/LibExternal/Npgsql/BackendMessages/CommandCompleteMessage.cs @@ -0,0 +1,108 @@ +using System.Diagnostics; +using Npgsql.Internal; + +namespace Npgsql.BackendMessages; + +class CommandCompleteMessage : IBackendMessage +{ + internal StatementType StatementType { get; private set; } + internal uint OID { get; private set; } + internal ulong Rows { get; private set; } + + internal CommandCompleteMessage Load(NpgsqlReadBuffer buf, int len) + { + Rows = 0; + OID = 0; + + var bytes = buf.Buffer; + var i = buf.ReadPosition; + buf.Skip(len); + switch (bytes[i]) + { + case (byte)'I': + if (!AreEqual(bytes, i, "INSERT ")) + goto default; + StatementType = StatementType.Insert; + i += 7; + OID = (uint) ParseNumber(bytes, ref i); + i++; + Rows = ParseNumber(bytes, ref i); + return this; + + case (byte)'D': + if (!AreEqual(bytes, i, "DELETE ")) + goto default; + StatementType = StatementType.Delete; + i += 7; + Rows = ParseNumber(bytes, ref i); + return this; + + case (byte)'U': + if (!AreEqual(bytes, i, "UPDATE ")) + goto default; + StatementType = StatementType.Update; + i += 7; + Rows = ParseNumber(bytes, ref i); + return this; + + case (byte)'S': + if (!AreEqual(bytes, i, "SELECT ")) + goto default; + StatementType = StatementType.Select; + i += 7; + Rows = ParseNumber(bytes, ref i); + return this; + + case (byte)'M': + if (!AreEqual(bytes, i, "MOVE ")) + goto default; + StatementType = StatementType.Move; + i += 5; + Rows = ParseNumber(bytes, ref i); + return this; + + case (byte)'F': + if (!AreEqual(bytes, i, "FETCH ")) + goto default; + StatementType = StatementType.Fetch; + i += 6; + Rows = ParseNumber(bytes, ref i); + return this; + + case (byte)'C': + if (!AreEqual(bytes, i, "COPY ")) + goto default; + StatementType = StatementType.Copy; + i += 5; + Rows = ParseNumber(bytes, ref i); + return this; + + default: + StatementType = StatementType.Other; + return this; + } + } + + static bool AreEqual(byte[] bytes, int pos, string s) + { + for (var i = 0; i < s.Length; i++) + { + if (bytes[pos+i] != s[i]) + return false; + } + return true; + } + + static ulong ParseNumber(byte[] bytes, ref int pos) + { + Debug.Assert(bytes[pos] >= '0' && bytes[pos] <= '9'); + ulong result = 0; + do + { + result = result * 10 + bytes[pos++] - '0'; + } while (bytes[pos] >= '0' && bytes[pos] <= '9'); + return result; + } + + public BackendMessageCode Code => BackendMessageCode.CommandComplete; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/BackendMessages/CopyMessages.cs b/LibExternal/Npgsql/BackendMessages/CopyMessages.cs new file mode 100644 index 0000000..40d5e24 --- /dev/null +++ b/LibExternal/Npgsql/BackendMessages/CopyMessages.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using Npgsql.Internal; +using Npgsql.Util; + +namespace Npgsql.BackendMessages; + +abstract class CopyResponseMessageBase : IBackendMessage +{ + public abstract BackendMessageCode Code { get; } + + internal bool IsBinary { get; private set; } + internal short NumColumns { get; private set; } + internal List ColumnFormatCodes { get; } + + internal CopyResponseMessageBase() + { + ColumnFormatCodes = new List(); + } + + internal void Load(NpgsqlReadBuffer buf) + { + ColumnFormatCodes.Clear(); + + var binaryIndicator = buf.ReadByte(); + IsBinary = binaryIndicator switch + { + 0 => false, + 1 => true, + _ => throw new Exception("Invalid binary indicator in CopyInResponse message: " + binaryIndicator) + }; + + NumColumns = buf.ReadInt16(); + for (var i = 0; i < NumColumns; i++) + ColumnFormatCodes.Add((FormatCode)buf.ReadInt16()); + } +} + +class CopyInResponseMessage : CopyResponseMessageBase +{ + public override BackendMessageCode Code => BackendMessageCode.CopyInResponse; + + internal new CopyInResponseMessage Load(NpgsqlReadBuffer buf) + { + base.Load(buf); + return this; + } +} + +class CopyOutResponseMessage : CopyResponseMessageBase +{ + public override BackendMessageCode Code => BackendMessageCode.CopyOutResponse; + + internal new CopyOutResponseMessage Load(NpgsqlReadBuffer buf) + { + base.Load(buf); + return this; + } +} + +class CopyBothResponseMessage : CopyResponseMessageBase +{ + public override BackendMessageCode Code => BackendMessageCode.CopyBothResponse; + + internal new CopyBothResponseMessage Load(NpgsqlReadBuffer buf) + { + base.Load(buf); + return this; + } +} + +/// +/// Note that this message doesn't actually contain the data, but only the length. Data is processed +/// directly from the connector's buffer. +/// +class CopyDataMessage : IBackendMessage +{ + public BackendMessageCode Code => BackendMessageCode.CopyData; + + public int Length { get; private set; } + + internal CopyDataMessage Load(int len) + { + Length = len; + return this; + } +} + +class CopyDoneMessage : IBackendMessage +{ + public BackendMessageCode Code => BackendMessageCode.CopyDone; + internal static readonly CopyDoneMessage Instance = new(); + CopyDoneMessage() { } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/BackendMessages/DataRowMessage.cs b/LibExternal/Npgsql/BackendMessages/DataRowMessage.cs new file mode 100644 index 0000000..860d91e --- /dev/null +++ b/LibExternal/Npgsql/BackendMessages/DataRowMessage.cs @@ -0,0 +1,19 @@ +namespace Npgsql.BackendMessages; + +/// +/// DataRow is special in that it does not parse the actual contents of the backend message, +/// because in sequential mode the message will be traversed and processed sequentially by +/// . +/// +class DataRowMessage : IBackendMessage +{ + public BackendMessageCode Code => BackendMessageCode.DataRow; + + internal int Length { get; private set; } + + internal DataRowMessage Load(int len) + { + Length = len; + return this; + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/BackendMessages/EmptyQueryMessage.cs b/LibExternal/Npgsql/BackendMessages/EmptyQueryMessage.cs new file mode 100644 index 0000000..3f11cd2 --- /dev/null +++ b/LibExternal/Npgsql/BackendMessages/EmptyQueryMessage.cs @@ -0,0 +1,8 @@ +namespace Npgsql.BackendMessages; + +class EmptyQueryMessage : IBackendMessage +{ + public BackendMessageCode Code => BackendMessageCode.EmptyQueryResponse; + internal static readonly EmptyQueryMessage Instance = new(); + EmptyQueryMessage() { } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/BackendMessages/ErrorOrNoticeMessage.cs b/LibExternal/Npgsql/BackendMessages/ErrorOrNoticeMessage.cs new file mode 100644 index 0000000..ce2a963 --- /dev/null +++ b/LibExternal/Npgsql/BackendMessages/ErrorOrNoticeMessage.cs @@ -0,0 +1,188 @@ +using System; +using Npgsql.Internal; +using Npgsql.Logging; + +namespace Npgsql.BackendMessages; + +[Serializable] +class ErrorOrNoticeMessage +{ + internal string Severity { get; } + internal string InvariantSeverity { get; } + internal string SqlState { get; } + internal string Message { get; } + internal string? Detail { get; } + internal string? Hint { get; } + internal int Position { get; } + internal int InternalPosition { get; } + internal string? InternalQuery { get; } + internal string? Where { get; } + internal string? SchemaName { get; } + internal string? TableName { get; } + internal string? ColumnName { get; } + internal string? DataTypeName { get; } + internal string? ConstraintName { get; } + internal string? File { get; } + internal string? Line { get; } + internal string? Routine { get; } + + static readonly NpgsqlLogger Log = NpgsqlLogManager.CreateLogger(nameof(ErrorOrNoticeMessage)); + + // ReSharper disable once FunctionComplexityOverflow + internal static ErrorOrNoticeMessage Load(NpgsqlReadBuffer buf, bool includeDetail) + { + (string? severity, string? invariantSeverity, string? code, string? message, string? detail, string? hint) = (null, null, null, null, null, null); + var (position, internalPosition) = (0, 0); + (string? internalQuery, string? where) = (null, null); + (string? schemaName, string? tableName, string? columnName, string? dataTypeName, string? constraintName) = + (null, null, null, null, null); + (string? file, string? line, string? routine) = (null, null, null); + + while (true) + { + var fieldCode = (ErrorFieldTypeCode)buf.ReadByte(); + switch (fieldCode) { + case ErrorFieldTypeCode.Done: + // Null terminator; error message fully consumed. + goto End; + case ErrorFieldTypeCode.Severity: + severity = buf.ReadNullTerminatedStringRelaxed(); + break; + case ErrorFieldTypeCode.InvariantSeverity: + invariantSeverity = buf.ReadNullTerminatedStringRelaxed(); + break; + case ErrorFieldTypeCode.Code: + code = buf.ReadNullTerminatedStringRelaxed(); + break; + case ErrorFieldTypeCode.Message: + message = buf.ReadNullTerminatedStringRelaxed(); + break; + case ErrorFieldTypeCode.Detail: + detail = buf.ReadNullTerminatedStringRelaxed(); + if (!includeDetail && !string.IsNullOrEmpty(detail)) + detail = $"Detail redacted as it may contain sensitive data. Specify '{NpgsqlConnectionStringBuilder.IncludeExceptionDetailDisplayName}' in the connection string to include this information."; + break; + case ErrorFieldTypeCode.Hint: + hint = buf.ReadNullTerminatedStringRelaxed(); + break; + case ErrorFieldTypeCode.Position: + var positionStr = buf.ReadNullTerminatedStringRelaxed(); + if (!int.TryParse(positionStr, out var tmpPosition)) { + Log.Warn("Non-numeric position in ErrorResponse: " + positionStr); + continue; + } + position = tmpPosition; + break; + case ErrorFieldTypeCode.InternalPosition: + var internalPositionStr = buf.ReadNullTerminatedStringRelaxed(); + if (!int.TryParse(internalPositionStr, out var internalPositionTmp)) { + Log.Warn("Non-numeric position in ErrorResponse: " + internalPositionStr); + continue; + } + internalPosition = internalPositionTmp; + break; + case ErrorFieldTypeCode.InternalQuery: + internalQuery = buf.ReadNullTerminatedStringRelaxed(); + break; + case ErrorFieldTypeCode.Where: + where = buf.ReadNullTerminatedStringRelaxed(); + break; + case ErrorFieldTypeCode.File: + file = buf.ReadNullTerminatedStringRelaxed(); + break; + case ErrorFieldTypeCode.Line: + line = buf.ReadNullTerminatedStringRelaxed(); + break; + case ErrorFieldTypeCode.Routine: + routine = buf.ReadNullTerminatedStringRelaxed(); + break; + case ErrorFieldTypeCode.SchemaName: + schemaName = buf.ReadNullTerminatedStringRelaxed(); + break; + case ErrorFieldTypeCode.TableName: + tableName = buf.ReadNullTerminatedStringRelaxed(); + break; + case ErrorFieldTypeCode.ColumnName: + columnName = buf.ReadNullTerminatedStringRelaxed(); + break; + case ErrorFieldTypeCode.DataTypeName: + dataTypeName = buf.ReadNullTerminatedStringRelaxed(); + break; + case ErrorFieldTypeCode.ConstraintName: + constraintName = buf.ReadNullTerminatedStringRelaxed(); + break; + default: + // Unknown error field; consume and discard. + buf.ReadNullTerminatedStringRelaxed(); + break; + } + } + + End: + if (severity == null) + throw new NpgsqlException("Severity not received in server error message"); + if (code == null) + throw new NpgsqlException("Code not received in server error message"); + if (message == null) + throw new NpgsqlException("Message not received in server error message"); + + return new ErrorOrNoticeMessage( + severity, invariantSeverity ?? severity, code, message, + detail, hint, position, internalPosition, internalQuery, where, + schemaName, tableName, columnName, dataTypeName, constraintName, + file, line, routine); + + } + + internal ErrorOrNoticeMessage( + string severity, string invariantSeverity, string sqlState, string message, + string? detail = null, string? hint = null, int position = 0, int internalPosition = 0, string? internalQuery = null, string? where = null, + string? schemaName = null, string? tableName = null, string? columnName = null, string? dataTypeName = null, string? constraintName = null, + string? file = null, string? line = null, string? routine = null) + { + Severity = severity; + InvariantSeverity = invariantSeverity; + SqlState = sqlState; + Message = message; + Detail = detail; + Hint = hint; + Position = position; + InternalPosition = internalPosition; + InternalQuery = internalQuery; + Where = where; + SchemaName = schemaName; + TableName = tableName; + ColumnName = columnName; + DataTypeName = dataTypeName; + ConstraintName = constraintName; + File = file; + Line = line; + Routine = routine; + } + + /// + /// Error and notice message field codes + /// + internal enum ErrorFieldTypeCode : byte + { + Done = 0, + Severity = (byte)'S', + InvariantSeverity = (byte)'V', + Code = (byte)'C', + Message = (byte)'M', + Detail = (byte)'D', + Hint = (byte)'H', + Position = (byte)'P', + InternalPosition = (byte)'p', + InternalQuery = (byte)'q', + Where = (byte)'W', + SchemaName = (byte)'s', + TableName = (byte)'t', + ColumnName = (byte)'c', + DataTypeName = (byte)'d', + ConstraintName = (byte)'n', + File = (byte)'F', + Line = (byte)'L', + Routine = (byte)'R' + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/BackendMessages/NoDataMessage.cs b/LibExternal/Npgsql/BackendMessages/NoDataMessage.cs new file mode 100644 index 0000000..bb38904 --- /dev/null +++ b/LibExternal/Npgsql/BackendMessages/NoDataMessage.cs @@ -0,0 +1,8 @@ +namespace Npgsql.BackendMessages; + +class NoDataMessage : IBackendMessage +{ + public BackendMessageCode Code => BackendMessageCode.NoData; + internal static readonly NoDataMessage Instance = new(); + NoDataMessage() { } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/BackendMessages/ParameterDescriptionMessage.cs b/LibExternal/Npgsql/BackendMessages/ParameterDescriptionMessage.cs new file mode 100644 index 0000000..6cea943 --- /dev/null +++ b/LibExternal/Npgsql/BackendMessages/ParameterDescriptionMessage.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using Npgsql.Internal; + +namespace Npgsql.BackendMessages; + +class ParameterDescriptionMessage : IBackendMessage +{ + // ReSharper disable once InconsistentNaming + internal List TypeOIDs { get; } + + internal ParameterDescriptionMessage() + { + TypeOIDs = new List(); + } + + internal ParameterDescriptionMessage Load(NpgsqlReadBuffer buf) + { + var numParams = buf.ReadUInt16(); + TypeOIDs.Clear(); + for (var i = 0; i < numParams; i++) + TypeOIDs.Add(buf.ReadUInt32()); + return this; + } + + public BackendMessageCode Code => BackendMessageCode.ParameterDescription; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/BackendMessages/ParseCompleteMessage.cs b/LibExternal/Npgsql/BackendMessages/ParseCompleteMessage.cs new file mode 100644 index 0000000..aafbd92 --- /dev/null +++ b/LibExternal/Npgsql/BackendMessages/ParseCompleteMessage.cs @@ -0,0 +1,8 @@ +namespace Npgsql.BackendMessages; + +class ParseCompleteMessage : IBackendMessage +{ + public BackendMessageCode Code => BackendMessageCode.ParseComplete; + internal static readonly ParseCompleteMessage Instance = new(); + ParseCompleteMessage() { } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/BackendMessages/ReadyForQueryMessage.cs b/LibExternal/Npgsql/BackendMessages/ReadyForQueryMessage.cs new file mode 100644 index 0000000..76e5103 --- /dev/null +++ b/LibExternal/Npgsql/BackendMessages/ReadyForQueryMessage.cs @@ -0,0 +1,15 @@ +using Npgsql.Internal; + +namespace Npgsql.BackendMessages; + +class ReadyForQueryMessage : IBackendMessage +{ + public BackendMessageCode Code => BackendMessageCode.ReadyForQuery; + + internal TransactionStatus TransactionStatusIndicator { get; private set; } + + internal ReadyForQueryMessage Load(NpgsqlReadBuffer buf) { + TransactionStatusIndicator = (TransactionStatus)buf.ReadByte(); + return this; + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/BackendMessages/RowDescriptionMessage.cs b/LibExternal/Npgsql/BackendMessages/RowDescriptionMessage.cs new file mode 100644 index 0000000..346f826 --- /dev/null +++ b/LibExternal/Npgsql/BackendMessages/RowDescriptionMessage.cs @@ -0,0 +1,330 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using Npgsql.Internal; +using Npgsql.Internal.TypeHandlers; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using Npgsql.Replication.PgOutput.Messages; +using Npgsql.TypeMapping; +using Npgsql.Util; + +namespace Npgsql.BackendMessages; + +/// +/// A RowDescription message sent from the backend. +/// +/// +/// See https://www.postgresql.org/docs/current/static/protocol-message-formats.html +/// +sealed class RowDescriptionMessage : IBackendMessage, IReadOnlyList +{ + FieldDescription?[] _fields; + readonly Dictionary _nameIndex; + Dictionary? _insensitiveIndex; + + internal RowDescriptionMessage(int numFields = 10) + { + _fields = new FieldDescription[numFields]; + _nameIndex = new Dictionary(); + } + + RowDescriptionMessage(RowDescriptionMessage source) + { + Count = source.Count; + _fields = new FieldDescription?[Count]; + for (var i = 0; i < Count; i++) + _fields[i] = source._fields[i]!.Clone(); + _nameIndex = new Dictionary(source._nameIndex); + if (source._insensitiveIndex?.Count > 0) + _insensitiveIndex = new Dictionary(source._insensitiveIndex); + } + + internal RowDescriptionMessage Load(NpgsqlReadBuffer buf, ConnectorTypeMapper typeMapper) + { + _nameIndex.Clear(); + _insensitiveIndex?.Clear(); + + var numFields = Count = buf.ReadInt16(); + if (_fields.Length < numFields) + { + var oldFields = _fields; + _fields = new FieldDescription[numFields]; + Array.Copy(oldFields, _fields, oldFields.Length); + } + + for (var i = 0; i < numFields; ++i) + { + var field = _fields[i] ??= new(); + + field.Populate( + typeMapper, + name: buf.ReadNullTerminatedString(), + tableOID: buf.ReadUInt32(), + columnAttributeNumber: buf.ReadInt16(), + oid: buf.ReadUInt32(), + typeSize: buf.ReadInt16(), + typeModifier: buf.ReadInt32(), + formatCode: (FormatCode)buf.ReadInt16() + ); + + if (!_nameIndex.ContainsKey(field.Name)) + _nameIndex.Add(field.Name, i); + } + + return this; + } + + internal static RowDescriptionMessage CreateForReplication( + ConnectorTypeMapper typeMapper, uint tableOID, FormatCode formatCode, IReadOnlyList columns) + { + var msg = new RowDescriptionMessage(columns.Count); + var numFields = msg.Count = columns.Count; + + for (var i = 0; i < numFields; ++i) + { + var field = msg._fields[i] = new(); + var column = columns[i]; + + field.Populate( + typeMapper, + name: column.ColumnName, + tableOID: tableOID, + columnAttributeNumber: checked((short)i), + oid: column.DataTypeId, + typeSize: 0, // TODO: Confirm we don't have this in replication + typeModifier: column.TypeModifier, + formatCode: formatCode + ); + + if (!msg._nameIndex.ContainsKey(field.Name)) + msg._nameIndex.Add(field.Name, i); + } + + return msg; + } + + public FieldDescription this[int index] + { + get + { + Debug.Assert(index < Count); + Debug.Assert(_fields[index] != null); + + return _fields[index]!; + } + } + + public int Count { get; private set; } + + public IEnumerator GetEnumerator() => new Enumerator(this); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + /// Given a string name, returns the field's ordinal index in the row. + /// + internal int GetFieldIndex(string name) + => TryGetFieldIndex(name, out var ret) + ? ret + : throw new IndexOutOfRangeException("Field not found in row: " + name); + + /// + /// Given a string name, returns the field's ordinal index in the row. + /// + internal bool TryGetFieldIndex(string name, out int fieldIndex) + { + if (_nameIndex.TryGetValue(name, out fieldIndex)) + return true; + + if (_insensitiveIndex is null || _insensitiveIndex.Count == 0) + { + if (_insensitiveIndex == null) + _insensitiveIndex = new Dictionary(InsensitiveComparer.Instance); + + foreach (var kv in _nameIndex) + if (!_insensitiveIndex.ContainsKey(kv.Key)) + _insensitiveIndex[kv.Key] = kv.Value; + } + + return _insensitiveIndex.TryGetValue(name, out fieldIndex); + } + + public BackendMessageCode Code => BackendMessageCode.RowDescription; + + internal RowDescriptionMessage Clone() => new(this); + + /// + /// Comparer that's case-insensitive and Kana width-insensitive + /// + sealed class InsensitiveComparer : IEqualityComparer + { + public static readonly InsensitiveComparer Instance = new(); + static readonly CompareInfo CompareInfo = CultureInfo.InvariantCulture.CompareInfo; + + InsensitiveComparer() {} + + // We should really have CompareOptions.IgnoreKanaType here, but see + // https://github.com/dotnet/corefx/issues/12518#issuecomment-389658716 + public bool Equals(string? x, string? y) + => CompareInfo.Compare(x, y, CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase | CompareOptions.IgnoreKanaType) == 0; + + public int GetHashCode(string o) + => CompareInfo.GetSortKey(o, CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase | CompareOptions.IgnoreKanaType).GetHashCode(); + } + + class Enumerator : IEnumerator + { + readonly RowDescriptionMessage _rowDescription; + int _pos = -1; + + public Enumerator(RowDescriptionMessage rowDescription) + => _rowDescription = rowDescription; + + public FieldDescription Current + => _pos >= 0 ? _rowDescription[_pos] : throw new InvalidOperationException(); + + object IEnumerator.Current => Current; + + public bool MoveNext() + { + if (_pos == _rowDescription.Count - 1) + return false; + _pos++; + return true; + } + + public void Reset() => _pos = -1; + public void Dispose() {} + } +} + +/// +/// A descriptive record on a single field received from PostgreSQL. +/// See RowDescription in https://www.postgresql.org/docs/current/static/protocol-message-formats.html +/// +public sealed class FieldDescription +{ +#pragma warning disable CS8618 // Lazy-initialized type + internal FieldDescription() {} + + internal FieldDescription(uint oid) + : this("?", 0, 0, oid, 0, 0, FormatCode.Binary) {} + + internal FieldDescription( + string name, uint tableOID, short columnAttributeNumber, + uint oid, short typeSize, int typeModifier, FormatCode formatCode) + { + Name = name; + TableOID = tableOID; + ColumnAttributeNumber = columnAttributeNumber; + TypeOID = oid; + TypeSize = typeSize; + TypeModifier = typeModifier; + FormatCode = formatCode; + } +#pragma warning restore CS8618 + + internal FieldDescription(FieldDescription source) + { + _typeMapper = source._typeMapper; + Name = source.Name; + TableOID = source.TableOID; + ColumnAttributeNumber = source.ColumnAttributeNumber; + TypeOID = source.TypeOID; + TypeSize = source.TypeSize; + TypeModifier = source.TypeModifier; + FormatCode = source.FormatCode; + Handler = source.Handler; + } + + internal void Populate( + ConnectorTypeMapper typeMapper, string name, uint tableOID, short columnAttributeNumber, + uint oid, short typeSize, int typeModifier, FormatCode formatCode + ) + { + _typeMapper = typeMapper; + Name = name; + TableOID = tableOID; + ColumnAttributeNumber = columnAttributeNumber; + TypeOID = oid; + TypeSize = typeSize; + TypeModifier = typeModifier; + FormatCode = formatCode; + + ResolveHandler(); + } + + /// + /// The field name. + /// + internal string Name { get; set; } + + /// + /// The object ID of the field's data type. + /// + internal uint TypeOID { get; set; } + + /// + /// The data type size (see pg_type.typlen). Note that negative values denote variable-width types. + /// + public short TypeSize { get; set; } + + /// + /// The type modifier (see pg_attribute.atttypmod). The meaning of the modifier is type-specific. + /// + public int TypeModifier { get; set; } + + /// + /// If the field can be identified as a column of a specific table, the object ID of the table; otherwise zero. + /// + internal uint TableOID { get; set; } + + /// + /// If the field can be identified as a column of a specific table, the attribute number of the column; otherwise zero. + /// + internal short ColumnAttributeNumber { get; set; } + + /// + /// The format code being used for the field. + /// Currently will be zero (text) or one (binary). + /// In a RowDescription returned from the statement variant of Describe, the format code is not yet known and will always be zero. + /// + internal FormatCode FormatCode { get; set; } + + internal string TypeDisplayName => PostgresType.GetDisplayNameWithFacets(TypeModifier); + + /// + /// The Npgsql type handler assigned to handle this field. + /// Returns for fields with format text. + /// + internal NpgsqlTypeHandler Handler { get; private set; } + + internal PostgresType PostgresType + => _typeMapper.DatabaseInfo.ByOID.TryGetValue(TypeOID, out var postgresType) + ? postgresType + : UnknownBackendType.Instance; + + internal Type FieldType => Handler.GetFieldType(this); + + internal void ResolveHandler() + => Handler = IsBinaryFormat ? _typeMapper.ResolveByOID(TypeOID) : _typeMapper.UnrecognizedTypeHandler; + + ConnectorTypeMapper _typeMapper; + + internal bool IsBinaryFormat => FormatCode == FormatCode.Binary; + internal bool IsTextFormat => FormatCode == FormatCode.Text; + + internal FieldDescription Clone() + { + var field = new FieldDescription(this); + field.ResolveHandler(); + return field; + } + + /// + /// Returns a string that represents the current object. + /// + public override string ToString() => Name + (Handler == null ? "" : $"({Handler.PgDisplayName})"); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Common.cs b/LibExternal/Npgsql/Common.cs new file mode 100644 index 0000000..3885b11 --- /dev/null +++ b/LibExternal/Npgsql/Common.cs @@ -0,0 +1,79 @@ +namespace Npgsql; + +/// +/// Base class for all classes which represent a message sent by the PostgreSQL backend. +/// +interface IBackendMessage +{ + BackendMessageCode Code { get; } +} + +enum BackendMessageCode : byte +{ + AuthenticationRequest = (byte)'R', + BackendKeyData = (byte)'K', + BindComplete = (byte)'2', + CloseComplete = (byte)'3', + CommandComplete = (byte)'C', + CopyData = (byte)'d', + CopyDone = (byte)'c', + CopyBothResponse = (byte)'W', + CopyInResponse = (byte)'G', + CopyOutResponse = (byte)'H', + DataRow = (byte)'D', + EmptyQueryResponse = (byte)'I', + ErrorResponse = (byte)'E', + FunctionCall = (byte)'F', + FunctionCallResponse = (byte)'V', + NoData = (byte)'n', + NoticeResponse = (byte)'N', + NotificationResponse = (byte)'A', + ParameterDescription = (byte)'t', + ParameterStatus = (byte)'S', + ParseComplete = (byte)'1', + PasswordPacket = (byte)' ', + PortalSuspended = (byte)'s', + ReadyForQuery = (byte)'Z', + RowDescription = (byte)'T', +} + +static class FrontendMessageCode +{ + internal const byte Describe = (byte)'D'; + internal const byte Sync = (byte)'S'; + internal const byte Execute = (byte)'E'; + internal const byte Parse = (byte)'P'; + internal const byte Bind = (byte)'B'; + internal const byte Close = (byte)'C'; + internal const byte Query = (byte)'Q'; + internal const byte CopyData = (byte)'d'; + internal const byte CopyDone = (byte)'c'; + internal const byte CopyFail = (byte)'f'; + internal const byte Terminate = (byte)'X'; + internal const byte Password = (byte)'p'; +} + +enum StatementOrPortal : byte +{ + Statement = (byte)'S', + Portal = (byte)'P' +} + +/// +/// Specifies the type of SQL statement, e.g. SELECT +/// +public enum StatementType +{ +#pragma warning disable 1591 + Unknown, + Select, + Insert, + Delete, + Update, + CreateTableAs, + Move, + Fetch, + Copy, + Other +#pragma warning restore 1591 +} \ No newline at end of file diff --git a/LibExternal/Npgsql/ConnectorPool.cs b/LibExternal/Npgsql/ConnectorPool.cs new file mode 100644 index 0000000..cebb49e --- /dev/null +++ b/LibExternal/Npgsql/ConnectorPool.cs @@ -0,0 +1,443 @@ +using System; +using System.Data; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; +using System.Transactions; +using Npgsql.Internal; +using Npgsql.Logging; +using Npgsql.Util; + +namespace Npgsql; + +class ConnectorPool : ConnectorSource +{ + #region Fields and properties + + static readonly NpgsqlLogger Log = NpgsqlLogManager.CreateLogger(nameof(ConnectorPool)); + + readonly int _max; + readonly int _min; + readonly TimeSpan _connectionLifetime; + + volatile int _numConnectors; + + volatile int _idleCount; + + /// + /// Tracks all connectors currently managed by this pool, whether idle or busy. + /// Only updated rarely - when physical connections are opened/closed - but is read in perf-sensitive contexts. + /// + private protected readonly NpgsqlConnector?[] Connectors; + + readonly MultiHostConnectorPool? _parentPool; + + /// + /// Reader side for the idle connector channel. Contains nulls in order to release waiting attempts after + /// a connector has been physically closed/broken. + /// + readonly ChannelReader _idleConnectorReader; + internal ChannelWriter IdleConnectorWriter { get; } + + /// + /// Incremented every time this pool is cleared via or + /// . Allows us to identify connections which were + /// created before the clear. + /// + volatile int _clearCounter; + + static readonly TimerCallback PruningTimerCallback = PruneIdleConnectors; + readonly Timer _pruningTimer; + readonly TimeSpan _pruningSamplingInterval; + readonly int _pruningSampleSize; + readonly int[] _pruningSamples; + readonly int _pruningMedianIndex; + volatile bool _pruningTimerEnabled; + int _pruningSampleIndex; + + volatile int _isClearing; + + static readonly SingleThreadSynchronizationContext SingleThreadSynchronizationContext = new("NpgsqlRemainingAsyncSendWorker"); + + #endregion + + internal sealed override (int Total, int Idle, int Busy) Statistics + { + get + { + var numConnectors = _numConnectors; + var idleCount = _idleCount; + return (numConnectors, idleCount, numConnectors - idleCount); + } + } + + internal sealed override bool OwnsConnectors => true; + + internal ConnectorPool(NpgsqlConnectionStringBuilder settings, string connString, MultiHostConnectorPool? parentPool = null) + : base(settings, connString) + { + if (settings.MaxPoolSize < settings.MinPoolSize) + throw new ArgumentException($"Connection can't have 'Max Pool Size' {settings.MaxPoolSize} under 'Min Pool Size' {settings.MinPoolSize}"); + + _parentPool = parentPool; + + // We enforce Max Pool Size, so no need to to create a bounded channel (which is less efficient) + // On the consuming side, we have the multiplexing write loop but also non-multiplexing Rents + // On the producing side, we have connections being released back into the pool (both multiplexing and not) + var idleChannel = Channel.CreateUnbounded(); + _idleConnectorReader = idleChannel.Reader; + IdleConnectorWriter = idleChannel.Writer; + + _max = settings.MaxPoolSize; + _min = settings.MinPoolSize; + + if (settings.ConnectionPruningInterval == 0) + throw new ArgumentException("ConnectionPruningInterval can't be 0."); + var connectionIdleLifetime = TimeSpan.FromSeconds(settings.ConnectionIdleLifetime); + var pruningSamplingInterval = TimeSpan.FromSeconds(settings.ConnectionPruningInterval); + if (connectionIdleLifetime < pruningSamplingInterval) + throw new ArgumentException($"Connection can't have ConnectionIdleLifetime {connectionIdleLifetime} under ConnectionPruningInterval {_pruningSamplingInterval}"); + + _pruningTimer = new Timer(PruningTimerCallback, this, Timeout.Infinite, Timeout.Infinite); + _pruningSampleSize = DivideRoundingUp(settings.ConnectionIdleLifetime, settings.ConnectionPruningInterval); + _pruningMedianIndex = DivideRoundingUp(_pruningSampleSize, 2) - 1; // - 1 to go from length to index + _pruningSamplingInterval = pruningSamplingInterval; + _pruningSamples = new int[_pruningSampleSize]; + _pruningTimerEnabled = false; + + _connectionLifetime = TimeSpan.FromSeconds(settings.ConnectionLifetime); + Connectors = new NpgsqlConnector[_max]; + } + + internal sealed override ValueTask Get( + NpgsqlConnection conn, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken) + { + return TryGetIdleConnector(out var connector) + ? new ValueTask(connector) + : RentAsync(conn, timeout, async, cancellationToken); + + async ValueTask RentAsync( + NpgsqlConnection conn, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken) + { + // First, try to open a new physical connector. This will fail if we're at max capacity. + var connector = await OpenNewConnector(conn, timeout, async, cancellationToken); + if (connector != null) + return connector; + + // We're at max capacity. Block on the idle channel with a timeout. + // Note that Channels guarantee fair FIFO behavior to callers of ReadAsync (first-come first- + // served), which is crucial to us. + using var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + var finalToken = linkedSource.Token; + linkedSource.CancelAfter(timeout.CheckAndGetTimeLeft()); + + while (true) + { + try + { + if (async) + { + connector = await _idleConnectorReader.ReadAsync(finalToken); + if (CheckIdleConnector(connector)) + return connector; + } + else + { + // Channels don't have a sync API. To avoid sync-over-async issues, we use a special single- + // thread synchronization context which ensures that callbacks are executed on a dedicated + // thread. + // Note that AsTask isn't safe here for getting the result, since it still causes some continuation code + // to get executed on the TP (which can cause deadlocks). + using (SingleThreadSynchronizationContext.Enter()) + using (var mre = new ManualResetEventSlim()) + { + _idleConnectorReader.WaitToReadAsync(finalToken).GetAwaiter().OnCompleted(() => mre.Set()); + mre.Wait(finalToken); + } + } + } + catch (OperationCanceledException) + { + cancellationToken.ThrowIfCancellationRequested(); + Debug.Assert(finalToken.IsCancellationRequested); + throw new NpgsqlException( + $"The connection pool has been exhausted, either raise 'Max Pool Size' (currently {_max}) " + + $"or 'Timeout' (currently {Settings.Timeout} seconds) in your connection string.", + new TimeoutException()); + } + catch (ChannelClosedException) + { + throw new NpgsqlException("The connection pool has been shut down."); + } + + // If we're here, our waiting attempt on the idle connector channel was released with a null + // (or bad connector), or we're in sync mode. Check again if a new idle connector has appeared since we last checked. + if (TryGetIdleConnector(out connector)) + return connector; + + // We might have closed a connector in the meantime and no longer be at max capacity + // so try to open a new connector and if that fails, loop again. + connector = await OpenNewConnector(conn, timeout, async, cancellationToken); + if (connector != null) + return connector; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal sealed override bool TryGetIdleConnector([NotNullWhen(true)] out NpgsqlConnector? connector) + { + while (_idleConnectorReader.TryRead(out var nullableConnector)) + { + if (CheckIdleConnector(nullableConnector)) + { + connector = nullableConnector; + return true; + } + } + + connector = null; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + bool CheckIdleConnector([NotNullWhen(true)] NpgsqlConnector? connector) + { + if (connector is null) + return false; + + // Only decrement when the connector has a value. + Interlocked.Decrement(ref _idleCount); + + // An connector could be broken because of a keepalive that occurred while it was + // idling in the pool + // TODO: Consider removing the pool from the keepalive code. The following branch is simply irrelevant + // if keepalive isn't turned on. + if (connector.IsBroken) + { + CloseConnector(connector); + return false; + } + + if (_connectionLifetime != TimeSpan.Zero && DateTime.UtcNow > connector.OpenTimestamp + _connectionLifetime) + { + Log.Debug("Connection has exceeded its maximum lifetime and will be closed.", connector.Id); + CloseConnector(connector); + return false; + } + + Debug.Assert(connector.State == ConnectorState.Ready, + $"Got idle connector but {nameof(connector.State)} is {connector.State}"); + Debug.Assert(connector.CommandsInFlightCount == 0, + $"Got idle connector but {nameof(connector.CommandsInFlightCount)} is {connector.CommandsInFlightCount}"); + Debug.Assert(connector.MultiplexAsyncWritingLock == 0, + $"Got idle connector but {nameof(connector.MultiplexAsyncWritingLock)} is 1"); + + return true; + } + + internal sealed override async ValueTask OpenNewConnector( + NpgsqlConnection conn, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken) + { + // As long as we're under max capacity, attempt to increase the connector count and open a new connection. + for (var numConnectors = _numConnectors; numConnectors < _max; numConnectors = _numConnectors) + { + // Note that we purposefully don't use SpinWait for this: https://github.com/dotnet/coreclr/pull/21437 + if (Interlocked.CompareExchange(ref _numConnectors, numConnectors + 1, numConnectors) != numConnectors) + continue; + + try + { + // We've managed to increase the open counter, open a physical connections. + var connector = new NpgsqlConnector(this, conn) { ClearCounter = _clearCounter }; + await connector.Open(timeout, async, cancellationToken); + + var i = 0; + for (; i < _max; i++) + if (Interlocked.CompareExchange(ref Connectors[i], connector, null) == null) + break; + + Debug.Assert(i < _max, $"Could not find free slot in {Connectors} when opening."); + if (i == _max) + throw new NpgsqlException($"Could not find free slot in {Connectors} when opening. Please report a bug."); + + // Only start pruning if we've incremented open count past _min. + // Note that we don't do it only once, on equality, because the thread which incremented open count past _min might get exception + // on NpgsqlConnector.Open due to timeout, CancellationToken or other reasons. + if (numConnectors >= _min) + UpdatePruningTimer(); + + return connector; + } + catch + { + // Physical open failed, decrement the open and busy counter back down. + Interlocked.Decrement(ref _numConnectors); + + // In case there's a waiting attempt on the channel, we write a null to the idle connector channel + // to wake it up, so it will try opening (and probably throw immediately) + // Statement order is important since we have synchronous completions on the channel. + IdleConnectorWriter.TryWrite(null); + + // Just in case we always call UpdatePruningTimer for failed physical open + UpdatePruningTimer(); + + throw; + } + } + + return null; + } + + internal sealed override void Return(NpgsqlConnector connector) + { + Debug.Assert(!connector.InTransaction); + Debug.Assert(connector.MultiplexAsyncWritingLock == 0 || connector.IsBroken || connector.IsClosed, + $"About to return multiplexing connector to the pool, but {nameof(connector.MultiplexAsyncWritingLock)} is {connector.MultiplexAsyncWritingLock}"); + + // If Clear/ClearAll has been been called since this connector was first opened, + // throw it away. The same if it's broken (in which case CloseConnector is only + // used to update state/perf counter). + if (connector.ClearCounter != _clearCounter || connector.IsBroken) + { + CloseConnector(connector); + return; + } + + // Statement order is important since we have synchronous completions on the channel. + Interlocked.Increment(ref _idleCount); + var written = IdleConnectorWriter.TryWrite(connector); + Debug.Assert(written); + } + + internal override void Clear() + { + Interlocked.Increment(ref _clearCounter); + + if (Interlocked.CompareExchange(ref _isClearing, 1, 0) == 1) + return; + + try + { + var count = _idleCount; + while (count > 0 && _idleConnectorReader.TryRead(out var connector)) + { + if (CheckIdleConnector(connector)) + { + CloseConnector(connector); + count--; + } + } + } + finally + { + _isClearing = 0; + } + } + + void CloseConnector(NpgsqlConnector connector) + { + try + { + connector.Close(); + } + catch (Exception e) + { + Log.Warn("Exception while closing connector", e, connector.Id); + } + + var i = 0; + for (; i < _max; i++) + if (Interlocked.CompareExchange(ref Connectors[i], null, connector) == connector) + break; + + Debug.Assert(i < _max, $"Could not find free slot in {Connectors} when closing."); + if (i == _max) + throw new NpgsqlException($"Could not find free slot in {Connectors} when closing. Please report a bug."); + + var numConnectors = Interlocked.Decrement(ref _numConnectors); + Debug.Assert(numConnectors >= 0); + + // If a connector has been closed for any reason, we write a null to the idle connector channel to wake up + // a waiter, who will open a new physical connection + // Statement order is important since we have synchronous completions on the channel. + IdleConnectorWriter.TryWrite(null); + + // Only turn off the timer one time, when it was this Close that brought Open back to _min. + if (numConnectors == _min) + UpdatePruningTimer(); + } + + internal override bool TryRemovePendingEnlistedConnector(NpgsqlConnector connector, Transaction transaction) + => _parentPool is null + ? base.TryRemovePendingEnlistedConnector(connector, transaction) + : _parentPool.TryRemovePendingEnlistedConnector(connector, transaction); + + #region Pruning + + void UpdatePruningTimer() + { + lock (_pruningTimer) + { + var numConnectors = _numConnectors; + if (numConnectors > _min && !_pruningTimerEnabled) + { + _pruningTimerEnabled = true; + _pruningTimer.Change(_pruningSamplingInterval, Timeout.InfiniteTimeSpan); + } + else if (numConnectors <= _min && _pruningTimerEnabled) + { + _pruningTimer.Change(Timeout.Infinite, Timeout.Infinite); + _pruningSampleIndex = 0; + _pruningTimerEnabled = false; + } + } + } + + static void PruneIdleConnectors(object? state) + { + var pool = (ConnectorPool)state!; + var samples = pool._pruningSamples; + int toPrune; + lock (pool._pruningTimer) + { + // Check if we might have been contending with DisablePruning. + if (!pool._pruningTimerEnabled) + return; + + var sampleIndex = pool._pruningSampleIndex; + samples[sampleIndex] = pool._idleCount; + if (sampleIndex != pool._pruningSampleSize - 1) + { + pool._pruningSampleIndex = sampleIndex + 1; + pool._pruningTimer.Change(pool._pruningSamplingInterval, Timeout.InfiniteTimeSpan); + return; + } + + // Calculate median value for pruning, reset index and timer, and release the lock. + Array.Sort(samples); + toPrune = samples[pool._pruningMedianIndex]; + pool._pruningSampleIndex = 0; + pool._pruningTimer.Change(pool._pruningSamplingInterval, Timeout.InfiniteTimeSpan); + } + + while (toPrune > 0 && + pool._numConnectors > pool._min && + pool._idleConnectorReader.TryRead(out var connector) && + connector != null) + { + if (pool.CheckIdleConnector(connector)) + { + pool.CloseConnector(connector); + toPrune--; + } + } + } + + static int DivideRoundingUp(int value, int divisor) => 1 + (value - 1) / divisor; + + #endregion +} \ No newline at end of file diff --git a/LibExternal/Npgsql/ConnectorSource.cs b/LibExternal/Npgsql/ConnectorSource.cs new file mode 100644 index 0000000..8c359e4 --- /dev/null +++ b/LibExternal/Npgsql/ConnectorSource.cs @@ -0,0 +1,98 @@ +using Npgsql.Internal; +using Npgsql.Util; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using System.Transactions; + +namespace Npgsql; + +abstract class ConnectorSource : IDisposable +{ + internal NpgsqlConnectionStringBuilder Settings { get; } + + /// + /// Contains the connection string returned to the user from + /// after the connection has been opened. Does not contain the password unless Persist Security Info=true. + /// + internal string UserFacingConnectionString { get; } + + // Note that while the dictionary is protected by locking, we assume that the lists it contains don't need to be + // (i.e. access to connectors of a specific transaction won't be concurrent) + protected readonly Dictionary> _pendingEnlistedConnectors + = new(); + + internal abstract (int Total, int Idle, int Busy) Statistics { get; } + + internal ConnectorSource(NpgsqlConnectionStringBuilder settings, string connString) + { + Settings = settings; + + UserFacingConnectionString = settings.PersistSecurityInfo + ? connString + : settings.ToStringWithoutPassword(); + } + + internal abstract ValueTask Get( + NpgsqlConnection conn, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken); + + internal abstract bool TryGetIdleConnector([NotNullWhen(true)] out NpgsqlConnector? connector); + + internal abstract ValueTask OpenNewConnector( + NpgsqlConnection conn, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken); + + internal abstract void Return(NpgsqlConnector connector); + + internal abstract void Clear(); + + internal abstract bool OwnsConnectors { get; } + + public virtual void Dispose() { } + + #region Pending Enlisted Connections + + internal virtual void AddPendingEnlistedConnector(NpgsqlConnector connector, Transaction transaction) + { + lock (_pendingEnlistedConnectors) + { + if (!_pendingEnlistedConnectors.TryGetValue(transaction, out var list)) + list = _pendingEnlistedConnectors[transaction] = new List(); + list.Add(connector); + } + } + + internal virtual bool TryRemovePendingEnlistedConnector(NpgsqlConnector connector, Transaction transaction) + { + lock (_pendingEnlistedConnectors) + { + if (!_pendingEnlistedConnectors.TryGetValue(transaction, out var list)) + return false; + list.Remove(connector); + if (list.Count == 0) + _pendingEnlistedConnectors.Remove(transaction); + return true; + } + } + + internal virtual bool TryRentEnlistedPending(Transaction transaction, NpgsqlConnection connection, + [NotNullWhen(true)] out NpgsqlConnector? connector) + { + lock (_pendingEnlistedConnectors) + { + if (!_pendingEnlistedConnectors.TryGetValue(transaction, out var list)) + { + connector = null; + return false; + } + connector = list[list.Count - 1]; + list.RemoveAt(list.Count - 1); + if (list.Count == 0) + _pendingEnlistedConnectors.Remove(transaction); + return true; + } + } + + #endregion +} \ No newline at end of file diff --git a/LibExternal/Npgsql/ICancelable.cs b/LibExternal/Npgsql/ICancelable.cs new file mode 100644 index 0000000..460f17c --- /dev/null +++ b/LibExternal/Npgsql/ICancelable.cs @@ -0,0 +1,11 @@ +using System; +using System.Threading.Tasks; + +namespace Npgsql; + +interface ICancelable : IDisposable, IAsyncDisposable +{ + void Cancel(); + + Task CancelAsync(); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/ClusterStateCache.cs b/LibExternal/Npgsql/Internal/ClusterStateCache.cs new file mode 100644 index 0000000..8b7cb83 --- /dev/null +++ b/LibExternal/Npgsql/Internal/ClusterStateCache.cs @@ -0,0 +1,77 @@ +using Npgsql.Util; +using System; +using System.Collections.Concurrent; + +namespace Npgsql.Internal; + +/// +/// Cache for cluster's state +/// +public static class ClusterStateCache +{ + static readonly ConcurrentDictionary Clusters = new(); + + internal static ClusterState GetClusterState(string host, int port, bool ignoreExpiration) + => Clusters.TryGetValue(new(host, port), out var cs) && (ignoreExpiration || !cs.Timeout.HasExpired) + ? cs.State + : ClusterState.Unknown; + +#if NETSTANDARD2_0 + internal static ClusterState UpdateClusterState(string host, int port, ClusterState state, DateTime timeStamp, TimeSpan stateExpiration) + => Clusters.AddOrUpdate( + new ClusterIdentifier(host, port), + new ClusterInfo(state, new NpgsqlTimeout(stateExpiration), timeStamp), + (_, oldInfo) => oldInfo.TimeStamp >= timeStamp ? oldInfo : new ClusterInfo(state, new NpgsqlTimeout(stateExpiration), timeStamp)).State; +#else + internal static ClusterState UpdateClusterState(string host, int port, ClusterState state, DateTime timeStamp, TimeSpan stateExpiration) + => Clusters.AddOrUpdate( + new ClusterIdentifier(host, port), + static (_, newInfo) => newInfo, + static (_, oldInfo, newInfo) => oldInfo.TimeStamp >= newInfo.TimeStamp ? oldInfo : newInfo, + new ClusterInfo(state, new NpgsqlTimeout(stateExpiration), timeStamp)).State; +#endif + + /// + /// Removes the specified cluster's state from cache + /// + /// Host address + /// Host port + public static void RemoveClusterState(string host, int port) + => Clusters.TryRemove(new ClusterIdentifier(host, port), out _); + + /// + /// Removes every cluster's state from cache + /// + public static void Clear() => Clusters.Clear(); + + readonly struct ClusterIdentifier : IEquatable + { + readonly string _host; + readonly int _port; + + public ClusterIdentifier(string host, int port) => (_host, _port) = (host, port); + public override bool Equals(object? obj) => obj is ClusterIdentifier other && Equals(other); + public bool Equals(ClusterIdentifier other) => _port == other._port && _host == other._host; + public override int GetHashCode() => HashCode.Combine(_host, _port); + } + + readonly struct ClusterInfo + { + internal readonly ClusterState State; + internal readonly NpgsqlTimeout Timeout; + // While the TimeStamp is not strictly required, it does lower the risk of overwriting the current state with an old value + internal readonly DateTime TimeStamp; + + public ClusterInfo(ClusterState state, NpgsqlTimeout timeout, DateTime timeStamp) + => (State, Timeout, TimeStamp) = (state, timeout, timeStamp); + } +} + +enum ClusterState : byte +{ + Unknown = 0, + Offline = 1, + PrimaryReadWrite = 2, + PrimaryReadOnly = 3, + Standby = 4 +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/INpgsqlDatabaseInfoFactory.cs b/LibExternal/Npgsql/Internal/INpgsqlDatabaseInfoFactory.cs new file mode 100644 index 0000000..ccdb7a8 --- /dev/null +++ b/LibExternal/Npgsql/Internal/INpgsqlDatabaseInfoFactory.cs @@ -0,0 +1,22 @@ +using System.Threading.Tasks; +using Npgsql.Util; + +namespace Npgsql.Internal; + +/// +/// A factory which get generate instances of , which describe a database +/// and the types it contains. When first connecting to a database, Npgsql will attempt to load information +/// about it via this factory. +/// +public interface INpgsqlDatabaseInfoFactory +{ + /// + /// Given a connection, loads all necessary information about the connected database, e.g. its types. + /// A factory should only handle the exact database type it was meant for, and return null otherwise. + /// + /// + /// An object describing the database to which is connected, or null if the + /// database isn't of the correct type and isn't handled by this factory. + /// + Task Load(NpgsqlConnector conn, NpgsqlTimeout timeout, bool async); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/NpgsqlConnector.Auth.cs b/LibExternal/Npgsql/Internal/NpgsqlConnector.Auth.cs new file mode 100644 index 0000000..b4ae3cd --- /dev/null +++ b/LibExternal/Npgsql/Internal/NpgsqlConnector.Auth.cs @@ -0,0 +1,477 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.Util; +using static Npgsql.Util.Statics; + +namespace Npgsql.Internal; + +partial class NpgsqlConnector +{ + async Task Authenticate(string username, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken) + { + Log.Trace("Authenticating...", Id); + + timeout.CheckAndApply(this); + var msg = Expect(await ReadMessage(async), this); + switch (msg.AuthRequestType) + { + case AuthenticationRequestType.AuthenticationOk: + return; + + case AuthenticationRequestType.AuthenticationCleartextPassword: + await AuthenticateCleartext(username, async, cancellationToken); + return; + + case AuthenticationRequestType.AuthenticationMD5Password: + await AuthenticateMD5(username, ((AuthenticationMD5PasswordMessage)msg).Salt, async, cancellationToken); + return; + + case AuthenticationRequestType.AuthenticationSASL: + await AuthenticateSASL(((AuthenticationSASLMessage)msg).Mechanisms, username, async, cancellationToken); + return; + + case AuthenticationRequestType.AuthenticationGSS: + case AuthenticationRequestType.AuthenticationSSPI: + await AuthenticateGSS(async); + return; + + case AuthenticationRequestType.AuthenticationGSSContinue: + throw new NpgsqlException("Can't start auth cycle with AuthenticationGSSContinue"); + + default: + throw new NotSupportedException($"Authentication method not supported (Received: {msg.AuthRequestType})"); + } + } + + async Task AuthenticateCleartext(string username, bool async, CancellationToken cancellationToken = default) + { + var passwd = GetPassword(username); + if (passwd == null) + throw new NpgsqlException("No password has been provided but the backend requires one (in cleartext)"); + + var encoded = new byte[Encoding.UTF8.GetByteCount(passwd) + 1]; + Encoding.UTF8.GetBytes(passwd, 0, passwd.Length, encoded, 0); + + await WritePassword(encoded, async, cancellationToken); + await Flush(async, cancellationToken); + Expect(await ReadMessage(async), this); + } + + async Task AuthenticateSASL(List mechanisms, string username, bool async, CancellationToken cancellationToken = default) + { + // At the time of writing PostgreSQL only supports SCRAM-SHA-256 and SCRAM-SHA-256-PLUS + var supportsSha256 = mechanisms.Contains("SCRAM-SHA-256"); + var supportsSha256Plus = mechanisms.Contains("SCRAM-SHA-256-PLUS"); + if (!supportsSha256 && !supportsSha256Plus) + throw new NpgsqlException("No supported SASL mechanism found (only SCRAM-SHA-256 and SCRAM-SHA-256-PLUS are supported for now). " + + "Mechanisms received from server: " + string.Join(", ", mechanisms)); + + var mechanism = string.Empty; + var cbindFlag = string.Empty; + var cbind = string.Empty; + var successfulBind = false; + + if (supportsSha256Plus) + { + var sslStream = (SslStream)_stream; + if (sslStream.RemoteCertificate is null) + { + Log.Warn("Remote certificate null, falling back to SCRAM-SHA-256"); + } + else + { + using var remoteCertificate = new X509Certificate2(sslStream.RemoteCertificate); + // Checking for hashing algorithms + HashAlgorithm? hashAlgorithm = null; + var algorithmName = remoteCertificate.SignatureAlgorithm.FriendlyName; + if (algorithmName is null) + { + Log.Warn("Signature algorithm was null, falling back to SCRAM-SHA-256"); + } + else if (algorithmName.StartsWith("sha1", StringComparison.OrdinalIgnoreCase) || + algorithmName.StartsWith("md5", StringComparison.OrdinalIgnoreCase) || + algorithmName.StartsWith("sha256", StringComparison.OrdinalIgnoreCase)) + { + hashAlgorithm = SHA256.Create(); + } + else if (algorithmName.StartsWith("sha384", StringComparison.OrdinalIgnoreCase)) + { + hashAlgorithm = SHA384.Create(); + } + else if (algorithmName.StartsWith("sha512", StringComparison.OrdinalIgnoreCase)) + { + hashAlgorithm = SHA512.Create(); + } + else + { + Log.Warn($"Support for signature algorithm {algorithmName} is not yet implemented, falling back to SCRAM-SHA-256"); + } + + if (hashAlgorithm != null) + { + using var _ = hashAlgorithm; + + // RFC 5929 + mechanism = "SCRAM-SHA-256-PLUS"; + // PostgreSQL only supports tls-server-end-point binding + cbindFlag = "p=tls-server-end-point"; + // SCRAM-SHA-256-PLUS depends on using ssl stream, so it's fine + var cbindFlagBytes = Encoding.UTF8.GetBytes($"{cbindFlag},,"); + + var certificateHash = hashAlgorithm.ComputeHash(remoteCertificate.GetRawCertData()); + var cbindBytes = cbindFlagBytes.Concat(certificateHash).ToArray(); + cbind = Convert.ToBase64String(cbindBytes); + successfulBind = true; + IsScramPlus = true; + } + } + } + + if (!successfulBind && supportsSha256) + { + mechanism = "SCRAM-SHA-256"; + // We can get here if PostgreSQL supports only SCRAM-SHA-256 or there was an error while binding to SCRAM-SHA-256-PLUS + // So, we set 'n' (client does not support binding) if there was an error while binding + // or 'y' (client supports but server doesn't) in other case + cbindFlag = supportsSha256Plus ? "n" : "y"; + cbind = supportsSha256Plus ? "biws" : "eSws"; + successfulBind = true; + IsScram = true; + } + + if (!successfulBind) + { + // We can get here if PostgreSQL supports only SCRAM-SHA-256-PLUS but there was an error while binding to it + throw new NpgsqlException("Unable to bind to SCRAM-SHA-256-PLUS, check logs for more information"); + } + + var passwd = GetPassword(username) ?? + throw new NpgsqlException($"No password has been provided but the backend requires one (in SASL/{mechanism})"); + + // Assumption: the write buffer is big enough to contain all our outgoing messages + var clientNonce = GetNonce(); + + await WriteSASLInitialResponse(mechanism, PGUtil.UTF8Encoding.GetBytes($"{cbindFlag},,n=*,r={clientNonce}"), async, cancellationToken); + await Flush(async, cancellationToken); + + var saslContinueMsg = Expect(await ReadMessage(async), this); + if (saslContinueMsg.AuthRequestType != AuthenticationRequestType.AuthenticationSASLContinue) + throw new NpgsqlException("[SASL] AuthenticationSASLContinue message expected"); + var firstServerMsg = AuthenticationSCRAMServerFirstMessage.Load(saslContinueMsg.Payload); + if (!firstServerMsg.Nonce.StartsWith(clientNonce, StringComparison.Ordinal)) + throw new NpgsqlException("[SCRAM] Malformed SCRAMServerFirst message: server nonce doesn't start with client nonce"); + + var saltBytes = Convert.FromBase64String(firstServerMsg.Salt); + var saltedPassword = Hi(passwd.Normalize(NormalizationForm.FormKC), saltBytes, firstServerMsg.Iteration); + + var clientKey = HMAC(saltedPassword, "Client Key"); + byte[] storedKey; + using (var sha256 = SHA256.Create()) + storedKey = sha256.ComputeHash(clientKey); + + var clientFirstMessageBare = $"n=*,r={clientNonce}"; + var serverFirstMessage = $"r={firstServerMsg.Nonce},s={firstServerMsg.Salt},i={firstServerMsg.Iteration}"; + var clientFinalMessageWithoutProof = $"c={cbind},r={firstServerMsg.Nonce}"; + + var authMessage = $"{clientFirstMessageBare},{serverFirstMessage},{clientFinalMessageWithoutProof}"; + + var clientSignature = HMAC(storedKey, authMessage); + var clientProofBytes = Xor(clientKey, clientSignature); + var clientProof = Convert.ToBase64String(clientProofBytes); + + var serverKey = HMAC(saltedPassword, "Server Key"); + var serverSignature = HMAC(serverKey, authMessage); + + var messageStr = $"{clientFinalMessageWithoutProof},p={clientProof}"; + + await WriteSASLResponse(Encoding.UTF8.GetBytes(messageStr), async, cancellationToken); + await Flush(async, cancellationToken); + + var saslFinalServerMsg = Expect(await ReadMessage(async), this); + if (saslFinalServerMsg.AuthRequestType != AuthenticationRequestType.AuthenticationSASLFinal) + throw new NpgsqlException("[SASL] AuthenticationSASLFinal message expected"); + + var scramFinalServerMsg = AuthenticationSCRAMServerFinalMessage.Load(saslFinalServerMsg.Payload); + if (scramFinalServerMsg.ServerSignature != Convert.ToBase64String(serverSignature)) + throw new NpgsqlException("[SCRAM] Unable to verify server signature"); + + var okMsg = Expect(await ReadMessage(async), this); + if (okMsg.AuthRequestType != AuthenticationRequestType.AuthenticationOk) + throw new NpgsqlException("[SASL] Expected AuthenticationOK message"); + + static string GetNonce() + { + using var rncProvider = RandomNumberGenerator.Create(); + var nonceBytes = new byte[18]; + + rncProvider.GetBytes(nonceBytes); + return Convert.ToBase64String(nonceBytes); + } + + static byte[] Hi(string str, byte[] salt, int count) + { + using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(str)); + var salt1 = new byte[salt.Length + 4]; + byte[] hi, u1; + + Buffer.BlockCopy(salt, 0, salt1, 0, salt.Length); + salt1[salt1.Length - 1] = 1; + + hi = u1 = hmac.ComputeHash(salt1); + + for (var i = 1; i < count; i++) + { + var u2 = hmac.ComputeHash(u1); + Xor(hi, u2); + u1 = u2; + } + + return hi; + } + + static byte[] Xor(byte[] buffer1, byte[] buffer2) + { + for (var i = 0; i < buffer1.Length; i++) + buffer1[i] ^= buffer2[i]; + return buffer1; + } + + static byte[] HMAC(byte[] data, string key) + { + using var hmacsha256 = new HMACSHA256(data); + return hmacsha256.ComputeHash(Encoding.UTF8.GetBytes(key)); + } + } + + async Task AuthenticateMD5(string username, byte[] salt, bool async, CancellationToken cancellationToken = default) + { + var passwd = GetPassword(username); + if (passwd == null) + throw new NpgsqlException("No password has been provided but the backend requires one (in MD5)"); + + byte[] result; + using (var md5 = MD5.Create()) + { + // First phase + var passwordBytes = PGUtil.UTF8Encoding.GetBytes(passwd); + var usernameBytes = PGUtil.UTF8Encoding.GetBytes(username); + var cryptBuf = new byte[passwordBytes.Length + usernameBytes.Length]; + passwordBytes.CopyTo(cryptBuf, 0); + usernameBytes.CopyTo(cryptBuf, passwordBytes.Length); + + var sb = new StringBuilder(); + var hashResult = md5.ComputeHash(cryptBuf); + foreach (var b in hashResult) + sb.Append(b.ToString("x2")); + + var prehash = sb.ToString(); + + var prehashbytes = PGUtil.UTF8Encoding.GetBytes(prehash); + cryptBuf = new byte[prehashbytes.Length + 4]; + + Array.Copy(salt, 0, cryptBuf, prehashbytes.Length, 4); + + // 2. + prehashbytes.CopyTo(cryptBuf, 0); + + sb = new StringBuilder("md5"); + hashResult = md5.ComputeHash(cryptBuf); + foreach (var b in hashResult) + sb.Append(b.ToString("x2")); + + var resultString = sb.ToString(); + result = new byte[Encoding.UTF8.GetByteCount(resultString) + 1]; + Encoding.UTF8.GetBytes(resultString, 0, resultString.Length, result, 0); + result[result.Length - 1] = 0; + } + + await WritePassword(result, async, cancellationToken); + await Flush(async, cancellationToken); + Expect(await ReadMessage(async), this); + } + + async Task AuthenticateGSS(bool async) + { + if (!IntegratedSecurity) + throw new NpgsqlException("GSS/SSPI authentication but IntegratedSecurity not enabled"); + + using var negotiateStream = new NegotiateStream(new GSSPasswordMessageStream(this), true); + try + { + var targetName = $"{KerberosServiceName}/{Host}"; + if (async) + await negotiateStream.AuthenticateAsClientAsync(CredentialCache.DefaultNetworkCredentials, targetName); + else + negotiateStream.AuthenticateAsClient(CredentialCache.DefaultNetworkCredentials, targetName); + } + catch (AuthenticationCompleteException) + { + return; + } + catch (IOException e) when (e.InnerException is AuthenticationCompleteException) + { + return; + } + catch (IOException e) when (e.InnerException is PostgresException) + { + throw e.InnerException; + } + + throw new NpgsqlException("NegotiateStream.AuthenticateAsClient completed unexpectedly without signaling success"); + } + + /// + /// This Stream is placed between NegotiateStream and the socket's NetworkStream (or SSLStream). It intercepts + /// traffic and performs the following operations: + /// * Outgoing messages are framed in PostgreSQL's PasswordMessage, and incoming are stripped of it. + /// * NegotiateStream frames payloads with a 5-byte header, which PostgreSQL doesn't understand. This header is + /// stripped from outgoing messages and added to incoming ones. + /// + /// + /// See https://referencesource.microsoft.com/#System/net/System/Net/_StreamFramer.cs,16417e735f0e9530,references + /// + class GSSPasswordMessageStream : Stream + { + readonly NpgsqlConnector _connector; + int _leftToWrite; + int _leftToRead, _readPos; + byte[]? _readBuf; + + internal GSSPasswordMessageStream(NpgsqlConnector connector) + => _connector = connector; + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default) + => Write(buffer, offset, count, true, cancellationToken); + + public override void Write(byte[] buffer, int offset, int count) + => Write(buffer, offset, count, false).GetAwaiter().GetResult(); + + async Task Write(byte[] buffer, int offset, int count, bool async, CancellationToken cancellationToken = default) + { + if (_leftToWrite == 0) + { + // We're writing the frame header, which contains the payload size. + _leftToWrite = (buffer[3] << 8) | buffer[4]; + + buffer[0] = 22; + if (buffer[1] != 1) + throw new NotSupportedException($"Received frame header major v {buffer[1]} (different from 1)"); + if (buffer[2] != 0) + throw new NotSupportedException($"Received frame header minor v {buffer[2]} (different from 0)"); + + // In case of payload data in the same buffer just after the frame header + if (count == 5) + return; + count -= 5; + offset += 5; + } + + if (count > _leftToWrite) + throw new NpgsqlException($"NegotiateStream trying to write {count} bytes but according to frame header we only have {_leftToWrite} left!"); + await _connector.WritePassword(buffer, offset, count, async, cancellationToken); + await _connector.Flush(async, cancellationToken); + _leftToWrite -= count; + } + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default) + => Read(buffer, offset, count, true, cancellationToken); + + public override int Read(byte[] buffer, int offset, int count) + => Read(buffer, offset, count, false).GetAwaiter().GetResult(); + + async Task Read(byte[] buffer, int offset, int count, bool async, CancellationToken cancellationToken = default) + { + if (_leftToRead == 0) + { + var response = Expect(await _connector.ReadMessage(async), _connector); + if (response.AuthRequestType == AuthenticationRequestType.AuthenticationOk) + throw new AuthenticationCompleteException(); + var gssMsg = response as AuthenticationGSSContinueMessage; + if (gssMsg == null) + throw new NpgsqlException($"Received unexpected authentication request message {response.AuthRequestType}"); + _readBuf = gssMsg.AuthenticationData; + _leftToRead = gssMsg.AuthenticationData.Length; + _readPos = 0; + buffer[0] = 22; + buffer[1] = 1; + buffer[2] = 0; + buffer[3] = (byte)((_leftToRead >> 8) & 0xFF); + buffer[4] = (byte)(_leftToRead & 0xFF); + return 5; + } + + if (count > _leftToRead) + throw new NpgsqlException($"NegotiateStream trying to read {count} bytes but according to frame header we only have {_leftToRead} left!"); + count = Math.Min(count, _leftToRead); + Array.Copy(_readBuf!, _readPos, buffer, offset, count); + _leftToRead -= count; + return count; + } + + public override void Flush() { } + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); + + public override bool CanRead => true; + public override bool CanWrite => true; + public override bool CanSeek => false; + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + } + + class AuthenticationCompleteException : Exception { } + + string? GetPassword(string username) + { + var password = Settings.Password; + if (password != null) + return password; + + if (ProvidePasswordCallback is { } passwordCallback) + try + { + Log.Trace($"Taking password from {nameof(ProvidePasswordCallback)} delegate"); + password = passwordCallback(Host, Port, Settings.Database!, username); + } + catch (Exception e) + { + throw new NpgsqlException($"Obtaining password using {nameof(NpgsqlConnection)}.{nameof(ProvidePasswordCallback)} delegate failed", e); + } + + if (password is null) + password = PostgresEnvironment.Password; + + if (password != null) + return password; + + var passFile = Settings.Passfile ?? PostgresEnvironment.PassFile ?? PostgresEnvironment.PassFileDefault; + if (passFile != null) + { + var matchingEntry = new PgPassFile(passFile!) + .GetFirstMatchingEntry(Host, Port, Settings.Database!, username); + if (matchingEntry != null) + { + Log.Trace("Taking password from pgpass file"); + password = matchingEntry.Password; + } + } + + return password; + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/NpgsqlConnector.FrontendMessages.cs b/LibExternal/Npgsql/Internal/NpgsqlConnector.FrontendMessages.cs new file mode 100644 index 0000000..c38f395 --- /dev/null +++ b/LibExternal/Npgsql/Internal/NpgsqlConnector.FrontendMessages.cs @@ -0,0 +1,469 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.Util; +// ReSharper disable VariableHidesOuterVariable + +namespace Npgsql.Internal; + +partial class NpgsqlConnector +{ + internal Task WriteDescribe(StatementOrPortal statementOrPortal, string name, bool async, CancellationToken cancellationToken = default) + { + Debug.Assert(name.All(c => c < 128)); + + var len = sizeof(byte) + // Message code + sizeof(int) + // Length + sizeof(byte) + // Statement or portal + (name.Length + 1); // Statement/portal name + + if (WriteBuffer.WriteSpaceLeft < len) + return FlushAndWrite(len, statementOrPortal, name, async, cancellationToken); + + Write(len, statementOrPortal, name); + return Task.CompletedTask; + + async Task FlushAndWrite(int len, StatementOrPortal statementOrPortal, string name, bool async, CancellationToken cancellationToken) + { + await Flush(async, cancellationToken); + Debug.Assert(len <= WriteBuffer.WriteSpaceLeft, $"Message of type {GetType().Name} has length {len} which is bigger than the buffer ({WriteBuffer.WriteSpaceLeft})"); + Write(len, statementOrPortal, name); + } + + void Write(int len, StatementOrPortal statementOrPortal, string name) + { + WriteBuffer.WriteByte(FrontendMessageCode.Describe); + WriteBuffer.WriteInt32(len - 1); + WriteBuffer.WriteByte((byte)statementOrPortal); + WriteBuffer.WriteNullTerminatedString(name); + } + } + + internal Task WriteSync(bool async, CancellationToken cancellationToken = default) + { + const int len = sizeof(byte) + // Message code + sizeof(int); // Length + + if (WriteBuffer.WriteSpaceLeft < len) + return FlushAndWrite(async, cancellationToken); + + Write(); + return Task.CompletedTask; + + async Task FlushAndWrite(bool async, CancellationToken cancellationToken) + { + await Flush(async, cancellationToken); + Debug.Assert(len <= WriteBuffer.WriteSpaceLeft, $"Message of type {GetType().Name} has length {len} which is bigger than the buffer ({WriteBuffer.WriteSpaceLeft})"); + Write(); + } + + void Write() + { + WriteBuffer.WriteByte(FrontendMessageCode.Sync); + WriteBuffer.WriteInt32(len - 1); + } + } + + internal Task WriteExecute(int maxRows, bool async, CancellationToken cancellationToken = default) + { + // Note: non-empty portal currently not supported + + const int len = sizeof(byte) + // Message code + sizeof(int) + // Length + sizeof(byte) + // Null-terminated portal name (always empty for now) + sizeof(int); // Max number of rows + + if (WriteBuffer.WriteSpaceLeft < len) + return FlushAndWrite(maxRows, async, cancellationToken); + + Write(maxRows); + return Task.CompletedTask; + + async Task FlushAndWrite(int maxRows, bool async, CancellationToken cancellationToken) + { + await Flush(async, cancellationToken); + Debug.Assert(10 <= WriteBuffer.WriteSpaceLeft, $"Message of type {GetType().Name} has length 10 which is bigger than the buffer ({WriteBuffer.WriteSpaceLeft})"); + Write(maxRows); + } + + void Write(int maxRows) + { + WriteBuffer.WriteByte(FrontendMessageCode.Execute); + WriteBuffer.WriteInt32(len - 1); + WriteBuffer.WriteByte(0); // Portal is always empty for now + WriteBuffer.WriteInt32(maxRows); + } + } + + internal async Task WriteParse(string sql, string statementName, List inputParameters, bool async, CancellationToken cancellationToken = default) + { + Debug.Assert(statementName.All(c => c < 128)); + + int queryByteLen; + try + { + queryByteLen = TextEncoding.GetByteCount(sql); + } + catch (Exception e) + { + Break(e); + throw; + } + + if (WriteBuffer.WriteSpaceLeft < 1 + 4 + statementName.Length + 1) + await Flush(async, cancellationToken); + + var messageLength = + sizeof(byte) + // Message code + sizeof(int) + // Length + statementName.Length + // Statement name + sizeof(byte) + // Null terminator for the statement name + queryByteLen + sizeof(byte) + // SQL query length plus null terminator + sizeof(ushort) + // Number of parameters + inputParameters.Count * sizeof(int); // Parameter OIDs + + WriteBuffer.WriteByte(FrontendMessageCode.Parse); + WriteBuffer.WriteInt32(messageLength - 1); + WriteBuffer.WriteNullTerminatedString(statementName); + + await WriteBuffer.WriteString(sql, queryByteLen, async, cancellationToken); + + if (WriteBuffer.WriteSpaceLeft < 1 + 2) + await Flush(async, cancellationToken); + WriteBuffer.WriteByte(0); // Null terminator for the query + WriteBuffer.WriteUInt16((ushort)inputParameters.Count); + + foreach (var p in inputParameters) + { + if (WriteBuffer.WriteSpaceLeft < 4) + await Flush(async, cancellationToken); + + WriteBuffer.WriteInt32((int)p.Handler!.PostgresType.OID); + } + } + + internal async Task WriteBind( + List parameters, + string portal, + string statement, + bool allResultTypesAreUnknown, + bool[]? unknownResultTypeList, + bool async, + CancellationToken cancellationToken = default) + { + Debug.Assert(statement.All(c => c < 128)); + Debug.Assert(portal.All(c => c < 128)); + + var headerLength = + sizeof(byte) + // Message code + sizeof(int) + // Message length + sizeof(byte) + // Portal is always empty (only a null terminator) + statement.Length + sizeof(byte) + // Statement name plus null terminator + sizeof(ushort); // Number of parameter format codes that follow + + if (WriteBuffer.WriteSpaceLeft < headerLength) + { + Debug.Assert(WriteBuffer.Size >= headerLength, "Write buffer too small for Bind header"); + await Flush(async, cancellationToken); + } + + var formatCodesSum = 0; + var paramsLength = 0; + for (var paramIndex = 0; paramIndex < parameters.Count; paramIndex++) + { + var param = parameters[paramIndex]; + formatCodesSum += (int)param.FormatCode; + param.LengthCache?.Rewind(); + paramsLength += param.ValidateAndGetLength(); + } + + var formatCodeListLength = formatCodesSum == 0 ? 0 : formatCodesSum == parameters.Count ? 1 : parameters.Count; + + var messageLength = headerLength + + sizeof(short) * formatCodeListLength + // List of format codes + sizeof(short) + // Number of parameters + sizeof(int) * parameters.Count + // Parameter lengths + paramsLength + // Parameter values + sizeof(short) + // Number of result format codes + sizeof(short) * (unknownResultTypeList?.Length ?? 1); // Result format codes + + WriteBuffer.WriteByte(FrontendMessageCode.Bind); + WriteBuffer.WriteInt32(messageLength - 1); + Debug.Assert(portal == string.Empty); + WriteBuffer.WriteByte(0); // Portal is always empty + + WriteBuffer.WriteNullTerminatedString(statement); + WriteBuffer.WriteInt16(formatCodeListLength); + + // 0 length implicitly means all-text, 1 means all-binary, >1 means mix-and-match + if (formatCodeListLength == 1) + { + if (WriteBuffer.WriteSpaceLeft < 2) + await Flush(async, cancellationToken); + WriteBuffer.WriteInt16((short)FormatCode.Binary); + } + else if (formatCodeListLength > 1) + { + for (var paramIndex = 0; paramIndex < parameters.Count; paramIndex++) + { + if (WriteBuffer.WriteSpaceLeft < 2) + await Flush(async, cancellationToken); + WriteBuffer.WriteInt16((short)parameters[paramIndex].FormatCode); + } + } + + if (WriteBuffer.WriteSpaceLeft < 2) + await Flush(async, cancellationToken); + + WriteBuffer.WriteUInt16((ushort)parameters.Count); + + for (var paramIndex = 0; paramIndex < parameters.Count; paramIndex++) + { + var param = parameters[paramIndex]; + param.LengthCache?.Rewind(); + await param.WriteWithLength(WriteBuffer, async, cancellationToken); + } + + if (unknownResultTypeList != null) + { + if (WriteBuffer.WriteSpaceLeft < 2 + unknownResultTypeList.Length * 2) + await Flush(async, cancellationToken); + WriteBuffer.WriteInt16(unknownResultTypeList.Length); + foreach (var t in unknownResultTypeList) + WriteBuffer.WriteInt16(t ? 0 : 1); + } + else + { + if (WriteBuffer.WriteSpaceLeft < 4) + await Flush(async, cancellationToken); + WriteBuffer.WriteInt16(1); + WriteBuffer.WriteInt16(allResultTypesAreUnknown ? 0 : 1); + } + } + + internal Task WriteClose(StatementOrPortal type, string name, bool async, CancellationToken cancellationToken = default) + { + var len = sizeof(byte) + // Message code + sizeof(int) + // Length + sizeof(byte) + // Statement or portal + name.Length + sizeof(byte); // Statement or portal name plus null terminator + + if (WriteBuffer.WriteSpaceLeft < len) + return FlushAndWrite(len, type, name, async, cancellationToken); + + Write(len, type, name); + return Task.CompletedTask; + + async Task FlushAndWrite(int len, StatementOrPortal type, string name, bool async, CancellationToken cancellationToken) + { + await Flush(async, cancellationToken); + Debug.Assert(len <= WriteBuffer.WriteSpaceLeft, $"Message of type {GetType().Name} has length {len} which is bigger than the buffer ({WriteBuffer.WriteSpaceLeft})"); + Write(len, type, name); + } + + void Write(int len, StatementOrPortal type, string name) + { + WriteBuffer.WriteByte(FrontendMessageCode.Close); + WriteBuffer.WriteInt32(len - 1); + WriteBuffer.WriteByte((byte)type); + WriteBuffer.WriteNullTerminatedString(name); + } + } + + internal void WriteQuery(string sql) => WriteQuery(sql, false).GetAwaiter().GetResult(); + + internal async Task WriteQuery(string sql, bool async, CancellationToken cancellationToken = default) + { + var queryByteLen = TextEncoding.GetByteCount(sql); + + if (WriteBuffer.WriteSpaceLeft < 1 + 4) + await Flush(async, cancellationToken); + + WriteBuffer.WriteByte(FrontendMessageCode.Query); + WriteBuffer.WriteInt32( + sizeof(int) + // Message length (including self excluding code) + queryByteLen + // Query byte length + sizeof(byte)); // Null terminator + + await WriteBuffer.WriteString(sql, queryByteLen, async, cancellationToken); + if (WriteBuffer.WriteSpaceLeft < 1) + await Flush(async, cancellationToken); + WriteBuffer.WriteByte(0); // Null terminator + } + + internal void WriteCopyDone() => WriteCopyDone(false).GetAwaiter().GetResult(); + + internal async Task WriteCopyDone(bool async, CancellationToken cancellationToken = default) + { + const int len = sizeof(byte) + // Message code + sizeof(int); // Length + + if (WriteBuffer.WriteSpaceLeft < len) + await Flush(async, cancellationToken); + + WriteBuffer.WriteByte(FrontendMessageCode.CopyDone); + WriteBuffer.WriteInt32(len - 1); + } + + internal async Task WriteCopyFail(bool async, CancellationToken cancellationToken = default) + { + // Note: error message not supported for now + + const int len = sizeof(byte) + // Message code + sizeof(int) + // Length + sizeof(byte); // Error message is always empty (only a null terminator) + + if (WriteBuffer.WriteSpaceLeft < len) + await Flush(async, cancellationToken); + + WriteBuffer.WriteByte(FrontendMessageCode.CopyFail); + WriteBuffer.WriteInt32(len - 1); + WriteBuffer.WriteByte(0); // Error message is always empty (only a null terminator) + } + + internal void WriteCancelRequest(int backendProcessId, int backendSecretKey) + { + const int len = sizeof(int) + // Length + sizeof(int) + // Cancel request code + sizeof(int) + // Backend process id + sizeof(int); // Backend secret key + + Debug.Assert(backendProcessId != 0); + + if (WriteBuffer.WriteSpaceLeft < len) + Flush(false).GetAwaiter().GetResult(); + + WriteBuffer.WriteInt32(len); + WriteBuffer.WriteInt32(1234 << 16 | 5678); + WriteBuffer.WriteInt32(backendProcessId); + WriteBuffer.WriteInt32(backendSecretKey); + } + + internal void WriteTerminate() + { + const int len = sizeof(byte) + // Message code + sizeof(int); // Length + + if (WriteBuffer.WriteSpaceLeft < len) + Flush(false).GetAwaiter().GetResult(); + + WriteBuffer.WriteByte(FrontendMessageCode.Terminate); + WriteBuffer.WriteInt32(len - 1); + } + + internal void WriteSslRequest() + { + const int len = sizeof(int) + // Length + sizeof(int); // SSL request code + + if (WriteBuffer.WriteSpaceLeft < len) + Flush(false).GetAwaiter().GetResult(); + + WriteBuffer.WriteInt32(len); + WriteBuffer.WriteInt32(80877103); + } + + internal void WriteStartup(Dictionary parameters) + { + const int protocolVersion3 = 3 << 16; // 196608 + + var len = sizeof(int) + // Length + sizeof(int) + // Protocol version + sizeof(byte); // Trailing zero byte + + foreach (var kvp in parameters) + len += PGUtil.UTF8Encoding.GetByteCount(kvp.Key) + 1 + + PGUtil.UTF8Encoding.GetByteCount(kvp.Value) + 1; + + // Should really never happen, just in case + if (len > WriteBuffer.Size) + throw new Exception("Startup message bigger than buffer"); + + WriteBuffer.WriteInt32(len); + WriteBuffer.WriteInt32(protocolVersion3); + + foreach (var kv in parameters) + { + WriteBuffer.WriteString(kv.Key); + WriteBuffer.WriteByte(0); + WriteBuffer.WriteString(kv.Value); + WriteBuffer.WriteByte(0); + } + + WriteBuffer.WriteByte(0); + } + + #region Authentication + + internal Task WritePassword(byte[] payload, bool async, CancellationToken cancellationToken = default) => WritePassword(payload, 0, payload.Length, async, cancellationToken); + + internal async Task WritePassword(byte[] payload, int offset, int count, bool async, CancellationToken cancellationToken = default) + { + if (WriteBuffer.WriteSpaceLeft < sizeof(byte) + sizeof(int)) + await WriteBuffer.Flush(async, cancellationToken); + WriteBuffer.WriteByte(FrontendMessageCode.Password); + WriteBuffer.WriteInt32(sizeof(int) + count); + + if (count <= WriteBuffer.WriteSpaceLeft) + { + // The entire array fits in our WriteBuffer, copy it into the WriteBuffer as usual. + WriteBuffer.WriteBytes(payload, offset, count); + return; + } + + await WriteBuffer.Flush(async, cancellationToken); + await WriteBuffer.DirectWrite(new ReadOnlyMemory(payload, offset, count), async, cancellationToken); + } + + internal async Task WriteSASLInitialResponse(string mechanism, byte[] initialResponse, bool async, CancellationToken cancellationToken = default) + { + var len = sizeof(byte) + // Message code + sizeof(int) + // Length + PGUtil.UTF8Encoding.GetByteCount(mechanism) + sizeof(byte) + // Mechanism plus null terminator + sizeof(int) + // Initial response length + (initialResponse?.Length ?? 0); // Initial response payload + + if (WriteBuffer.WriteSpaceLeft < len) + await WriteBuffer.Flush(async, cancellationToken); + + WriteBuffer.WriteByte(FrontendMessageCode.Password); + WriteBuffer.WriteInt32(len - 1); + + WriteBuffer.WriteString(mechanism); + WriteBuffer.WriteByte(0); // null terminator + if (initialResponse == null) + WriteBuffer.WriteInt32(-1); + else + { + WriteBuffer.WriteInt32(initialResponse.Length); + WriteBuffer.WriteBytes(initialResponse); + } + } + + internal Task WriteSASLResponse(byte[] payload, bool async, CancellationToken cancellationToken = default) => WritePassword(payload, async, cancellationToken); + + #endregion Authentication + + internal Task WritePregenerated(byte[] data, bool async = false, CancellationToken cancellationToken = default) + { + if (WriteBuffer.WriteSpaceLeft < data.Length) + return FlushAndWrite(data, async, cancellationToken); + + WriteBuffer.WriteBytes(data, 0, data.Length); + return Task.CompletedTask; + + async Task FlushAndWrite(byte[] data, bool async, CancellationToken cancellationToken) + { + await Flush(async, cancellationToken); + Debug.Assert(data.Length <= WriteBuffer.WriteSpaceLeft, $"Pregenerated message has length {data.Length} which is bigger than the buffer ({WriteBuffer.WriteSpaceLeft})"); + WriteBuffer.WriteBytes(data, 0, data.Length); + } + } + + internal void Flush() => WriteBuffer.Flush(false).GetAwaiter().GetResult(); + + internal Task Flush(bool async, CancellationToken cancellationToken = default) => WriteBuffer.Flush(async, cancellationToken); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/NpgsqlConnector.cs b/LibExternal/Npgsql/Internal/NpgsqlConnector.cs new file mode 100644 index 0000000..b1deb22 --- /dev/null +++ b/LibExternal/Npgsql/Internal/NpgsqlConnector.cs @@ -0,0 +1,2843 @@ +using System.Buffers; +using System.Data; +using System.Diagnostics; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; +using System.Runtime.InteropServices; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Channels; +using System.Transactions; +using Npgsql.BackendMessages; +using Npgsql.Logging; +using Npgsql.TypeMapping; +using Npgsql.Util; +using static Npgsql.Util.Statics; + +namespace Npgsql.Internal; + +/// +/// Represents a connection to a PostgreSQL backend. Unlike NpgsqlConnection objects, which are +/// exposed to users, connectors are internal to Npgsql and are recycled by the connection pool. +/// +public sealed partial class NpgsqlConnector : IDisposable +{ + #region Fields and Properties + + /// + /// The physical connection socket to the backend. + /// + Socket _socket = default!; + + /// + /// The physical connection stream to the backend, without anything on top. + /// + NetworkStream _baseStream = default!; + + /// + /// The physical connection stream to the backend, layered with an SSL/TLS stream if in secure mode. + /// + Stream _stream = default!; + + /// + /// The parsed connection string. + /// + public NpgsqlConnectionStringBuilder Settings { get; } + + ProvideClientCertificatesCallback? ProvideClientCertificatesCallback { get; } + RemoteCertificateValidationCallback? UserCertificateValidationCallback { get; } + ProvidePasswordCallback? ProvidePasswordCallback { get; } + +#pragma warning disable CA2252 // Experimental API + PhysicalOpenCallback? PhysicalOpenCallback { get; } + PhysicalOpenAsyncCallback? PhysicalOpenAsyncCallback { get; } +#pragma warning restore CA2252 + + public Encoding TextEncoding { get; private set; } = default!; + + /// + /// Same as , except that it does not throw an exception if an invalid char is + /// encountered (exception fallback), but rather replaces it with a question mark character (replacement + /// fallback). + /// + internal Encoding RelaxedTextEncoding { get; private set; } = default!; + + /// + /// Buffer used for reading data. + /// + internal NpgsqlReadBuffer ReadBuffer { get; private set; } = default!; + + /// + /// If we read a data row that's bigger than , we allocate an oversize buffer. + /// The original (smaller) buffer is stored here, and restored when the connection is reset. + /// + NpgsqlReadBuffer? _origReadBuffer; + + /// + /// Buffer used for writing data. + /// + internal NpgsqlWriteBuffer WriteBuffer { get; private set; } = default!; + + /// + /// The secret key of the backend for this connector, used for query cancellation. + /// + int _backendSecretKey; + + /// + /// The process ID of the backend for this connector. + /// + internal int BackendProcessId { get; private set; } + + bool SupportsPostgresCancellation => BackendProcessId != 0; + + /// + /// A unique ID identifying this connector, used for logging. Currently mapped to BackendProcessId + /// + internal int Id => BackendProcessId; + + /// + /// Information about PostgreSQL and PostgreSQL-like databases (e.g. type definitions, capabilities...). + /// + public NpgsqlDatabaseInfo DatabaseInfo { get; internal set; } = default!; + + internal ConnectorTypeMapper TypeMapper { get; set; } = default!; + + /// + /// The current transaction status for this connector. + /// + internal TransactionStatus TransactionStatus { get; set; } + + /// + /// A transaction object for this connector. Since only one transaction can be in progress at any given time, + /// this instance is recycled. To check whether a transaction is currently in progress on this connector, + /// see . + /// + internal NpgsqlTransaction? Transaction { get; set; } + + internal NpgsqlTransaction? UnboundTransaction { get; set; } + + /// + /// The NpgsqlConnection that (currently) owns this connector. Null if the connector isn't + /// owned (i.e. idle in the pool) + /// + internal NpgsqlConnection? Connection { get; set; } + + /// + /// The number of messages that were prepended to the current message chain, but not yet sent. + /// Note that this only tracks messages which produce a ReadyForQuery message + /// + internal int PendingPrependedResponses { get; set; } + + /// + /// A ManualResetEventSlim used to make sure a cancellation request doesn't run + /// while we're reading responses for the prepended query + /// as we can't gracefully handle their cancellation. + /// + readonly ManualResetEventSlim ReadingPrependedMessagesMRE = new(initialState: true); + + internal NpgsqlDataReader? CurrentReader; + + internal PreparedStatementManager PreparedStatementManager { get; } + + internal SqlQueryParser SqlQueryParser { get; } = new(); + + /// + /// If the connector is currently in COPY mode, holds a reference to the importer/exporter object. + /// Otherwise null. + /// + internal ICancelable? CurrentCopyOperation; + + /// + /// Holds all run-time parameters received from the backend (via ParameterStatus messages) + /// + internal Dictionary PostgresParameters { get; } + + /// + /// Holds all run-time parameters in raw, binary format for efficient handling without allocations. + /// + readonly List<(byte[] Name, byte[] Value)> _rawParameters = new(); + + /// + /// If this connector was broken, this contains the exception that caused the break. + /// + volatile Exception? _breakReason; + + /// + /// Semaphore, used to synchronize DatabaseInfo between multiple connections, so it wouldn't be loaded in parallel. + /// + static readonly SemaphoreSlim DatabaseInfoSemaphore = new(1); + + /// + /// + /// Used by the pool to indicate that I/O is currently in progress on this connector, so that another write + /// isn't started concurrently. Note that since we have only one write loop, this is only ever usedto + /// protect against an over-capacity writes into a connector that's currently *asynchronously* writing. + /// + /// + /// It is guaranteed that the currently-executing + /// Specifically, reading may occur - and the connector may even be returned to the pool - before this is + /// released. + /// + /// + internal volatile int MultiplexAsyncWritingLock; + + /// + internal void FlagAsNotWritableForMultiplexing() + { + Debug.Assert(Settings.Multiplexing); + Debug.Assert(CommandsInFlightCount > 0 || IsBroken || IsClosed, + $"About to mark multiplexing connector as non-writable, but {nameof(CommandsInFlightCount)} is {CommandsInFlightCount}"); + + Interlocked.Exchange(ref MultiplexAsyncWritingLock, 1); + } + + /// + internal void FlagAsWritableForMultiplexing() + { + Debug.Assert(Settings.Multiplexing); + if (Interlocked.CompareExchange(ref MultiplexAsyncWritingLock, 0, 1) != 1) + throw new Exception("Multiplexing lock was not taken when releasing. Please report a bug."); + } + + /// + /// The timeout for reading messages that are part of the user's command + /// (i.e. which aren't internal prepended commands). + /// + /// Precision is milliseconds + internal int UserTimeout { private get; set; } + + /// + /// A lock that's taken while a user action is in progress, e.g. a command being executed. + /// Only used when keepalive is enabled, otherwise null. + /// + SemaphoreSlim? _userLock; + + /// + /// A lock that's taken while a cancellation is being delivered; new queries are blocked until the + /// cancellation is delivered. This reduces the chance that a cancellation meant for a previous + /// command will accidentally cancel a later one, see #615. + /// + object CancelLock { get; } = new(); + + /// + /// A lock that's taken to make sure no other concurrent operation is running. + /// Break takes it to set the state of the connector. + /// Anyone else should immediately check the state and exit + /// if the connector is closed. + /// + object SyncObj { get; } = new(); + + /// + /// A lock that's used to wait for the Cleanup to complete while breaking the connection. + /// + object CleanupLock { get; } = new(); + + readonly bool _isKeepAliveEnabled; + readonly Timer? _keepAliveTimer; + + /// + /// The command currently being executed by the connector, null otherwise. + /// Used only for concurrent use error reporting purposes. + /// + NpgsqlCommand? _currentCommand; + + bool _sendResetOnClose; + + /// + /// The connector source (e.g. pool) from where this connector came, and to which it will be returned. + /// Note that in multi-host scenarios, this references the host-specific rather than the + /// , + /// + readonly ConnectorSource _connectorSource; + + internal string UserFacingConnectionString => _connectorSource.UserFacingConnectionString; + + /// + /// Contains the UTC timestamp when this connector was opened, used to implement + /// . + /// + internal DateTime OpenTimestamp { get; private set; } + + internal int ClearCounter { get; set; } + + volatile bool _postgresCancellationPerformed; + internal bool PostgresCancellationPerformed + { + get => _postgresCancellationPerformed; + private set => _postgresCancellationPerformed = value; + } + + volatile bool _userCancellationRequested; + CancellationTokenRegistration _cancellationTokenRegistration; + internal bool UserCancellationRequested => _userCancellationRequested; + internal CancellationToken UserCancellationToken { get; set; } + internal bool AttemptPostgresCancellation { get; private set; } + static readonly TimeSpan _cancelImmediatelyTimeout = TimeSpan.FromMilliseconds(-1); + + X509Certificate2? _certificate; + + static readonly NpgsqlLogger Log = NpgsqlLogManager.CreateLogger(nameof(NpgsqlConnector)); + + internal readonly Stopwatch QueryLogStopWatch = new(); + + internal EndPoint? ConnectedEndPoint { get; private set; } + + #endregion + + #region Constants + + /// + /// The minimum timeout that can be set on internal commands such as COMMIT, ROLLBACK. + /// + /// Precision is seconds + internal const int MinimumInternalCommandTimeout = 3; + + #endregion + + #region Reusable Message Objects + + byte[]? _resetWithoutDeallocateMessage; + + int _resetWithoutDeallocateResponseCount; + + // Backend + readonly CommandCompleteMessage _commandCompleteMessage = new(); + readonly ReadyForQueryMessage _readyForQueryMessage = new(); + readonly ParameterDescriptionMessage _parameterDescriptionMessage = new(); + readonly DataRowMessage _dataRowMessage = new(); + readonly RowDescriptionMessage _rowDescriptionMessage = new(); + + // Since COPY is rarely used, allocate these lazily + CopyInResponseMessage? _copyInResponseMessage; + CopyOutResponseMessage? _copyOutResponseMessage; + CopyDataMessage? _copyDataMessage; + CopyBothResponseMessage? _copyBothResponseMessage; + + #endregion + + internal NpgsqlDataReader DataReader { get; set; } + + internal NpgsqlDataReader? UnboundDataReader { get; set; } + + #region Constructors + + internal NpgsqlConnector(ConnectorSource connectorSource, NpgsqlConnection conn) + : this(connectorSource) + { + ProvideClientCertificatesCallback = conn.ProvideClientCertificatesCallback; + UserCertificateValidationCallback = conn.UserCertificateValidationCallback; + ProvidePasswordCallback = conn.ProvidePasswordCallback; + +#pragma warning disable CA2252 // Experimental API + PhysicalOpenCallback = conn.PhysicalOpenCallback; + PhysicalOpenAsyncCallback = conn.PhysicalOpenAsyncCallback; +#pragma warning restore CA2252 + } + + NpgsqlConnector(NpgsqlConnector connector) + : this(connector._connectorSource) + { + ProvideClientCertificatesCallback = connector.ProvideClientCertificatesCallback; + UserCertificateValidationCallback = connector.UserCertificateValidationCallback; + ProvidePasswordCallback = connector.ProvidePasswordCallback; + } + + NpgsqlConnector(ConnectorSource connectorSource) + { + Debug.Assert(connectorSource.OwnsConnectors); + _connectorSource = connectorSource; + + State = ConnectorState.Closed; + TransactionStatus = TransactionStatus.Idle; + Settings = connectorSource.Settings; + PostgresParameters = new Dictionary(); + + _isKeepAliveEnabled = Settings.KeepAlive > 0; + if (_isKeepAliveEnabled) + { + _userLock = new SemaphoreSlim(1, 1); + _keepAliveTimer = new Timer(PerformKeepAlive, null, Timeout.Infinite, Timeout.Infinite); + } + + DataReader = new NpgsqlDataReader(this); + + // TODO: Not just for automatic preparation anymore... + PreparedStatementManager = new PreparedStatementManager(this); + + if (Settings.Multiplexing) + { + // Note: It's OK for this channel to be unbounded: each command enqueued to it is accompanied by sending + // it to PostgreSQL. If we overload it, a TCP zero window will make us block on the networking side + // anyway. + // Note: the in-flight channel can probably be single-writer, but that doesn't actually do anything + // at this point. And we currently rely on being able to complete the channel at any point (from + // Break). We may want to revisit this if an optimized, SingleWriter implementation is introduced. + var commandsInFlightChannel = Channel.CreateUnbounded( + new UnboundedChannelOptions { SingleReader = true }); + CommandsInFlightReader = commandsInFlightChannel.Reader; + CommandsInFlightWriter = commandsInFlightChannel.Writer; + + // TODO: Properly implement this + if (_isKeepAliveEnabled) + throw new NotImplementedException("Keepalive not yet implemented for multiplexing"); + } + } + + #endregion + + #region Configuration settings + + internal string Host => Settings.Host!; + internal int Port => Settings.Port; + internal string Database => Settings.Database!; + string KerberosServiceName => Settings.KerberosServiceName; + int ConnectionTimeout => Settings.Timeout; + bool IntegratedSecurity => Settings.IntegratedSecurity; + + /// + /// The actual command timeout value that gets set on internal commands. + /// + /// Precision is milliseconds + int InternalCommandTimeout + { + get + { + var internalTimeout = Settings.InternalCommandTimeout; + if (internalTimeout == -1) + return Math.Max(Settings.CommandTimeout, MinimumInternalCommandTimeout) * 1000; + + // Todo: Decide what we really want here + // This assertion can easily fail if InternalCommandTimeout is set to 1 or 2 in the connection string + // We probably don't want to allow these values but in that case a Debug.Assert is the wrong way to enforce it. + Debug.Assert(internalTimeout == 0 || internalTimeout >= MinimumInternalCommandTimeout); + return internalTimeout * 1000; + } + } + + #endregion Configuration settings + + #region State management + + int _state; + + /// + /// Gets the current state of the connector + /// + internal ConnectorState State + { + get => (ConnectorState)_state; + set + { + var newState = (int)value; + if (newState == _state) + return; + Interlocked.Exchange(ref _state, newState); + } + } + + /// + /// Returns whether the connector is open, regardless of any task it is currently performing + /// + bool IsConnected + => State switch + { + ConnectorState.Ready => true, + ConnectorState.Executing => true, + ConnectorState.Fetching => true, + ConnectorState.Waiting => true, + ConnectorState.Copy => true, + ConnectorState.Replication => true, + ConnectorState.Closed => false, + ConnectorState.Connecting => false, + ConnectorState.Broken => false, + _ => throw new ArgumentOutOfRangeException("Unknown state: " + State) + }; + + internal bool IsReady => State == ConnectorState.Ready; + internal bool IsClosed => State == ConnectorState.Closed; + internal bool IsBroken => State == ConnectorState.Broken; + + #endregion + + #region Open + + /// + /// Opens the physical connection to the server. + /// + /// Usually called by the RequestConnector + /// Method of the connection pool manager. + internal async Task Open(NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken) + { + Debug.Assert(State == ConnectorState.Closed); + + State = ConnectorState.Connecting; + + try + { + await OpenCore(this, Settings.SslMode, timeout, async, cancellationToken); + + await LoadDatabaseInfo(forceReload: false, timeout, async, cancellationToken); + + if (Settings.Pooling && !Settings.Multiplexing && !Settings.NoResetOnClose && DatabaseInfo.SupportsDiscard) + { + _sendResetOnClose = true; + GenerateResetMessage(); + } + + OpenTimestamp = DateTime.UtcNow; + Log.Trace($"Opened connection to {Host}:{Port}"); + +#pragma warning disable CA2252 // Experimental API + if (async && PhysicalOpenAsyncCallback is not null) + await PhysicalOpenAsyncCallback(this); + else if (!async && PhysicalOpenCallback is not null) + PhysicalOpenCallback(this); +#pragma warning restore CA2252 + + if (Settings.Multiplexing) + { + // Start an infinite async loop, which processes incoming multiplexing traffic. + // It is intentionally not awaited and will run as long as the connector is alive. + // The CommandsInFlightWriter channel is completed in Cleanup, which should cause this task + // to complete. + _ = Task.Run(MultiplexingReadLoop, CancellationToken.None) + .ContinueWith(t => + { + // Note that we *must* observe the exception if the task is faulted. + Log.Error("Exception bubbled out of multiplexing read loop", t.Exception!, Id); + }, TaskContinuationOptions.OnlyOnFaulted); + } + + if (_isKeepAliveEnabled) + { + // Start the keep alive mechanism to work by scheduling the timer. + // Otherwise, it doesn't work for cases when no query executed during + // the connection lifetime in case of a new connector. + lock (SyncObj) + { + var keepAlive = Settings.KeepAlive * 1000; + _keepAliveTimer!.Change(keepAlive, keepAlive); + } + } + } + catch (Exception e) + { + Break(e); + throw; + } + + static async Task OpenCore( + NpgsqlConnector conn, + SslMode sslMode, + NpgsqlTimeout timeout, + bool async, + CancellationToken cancellationToken, + bool isFirstAttempt = true) + { + await conn.RawOpen(sslMode, timeout, async, cancellationToken, isFirstAttempt); + + var username = conn.GetUsername(); + if (conn.Settings.Database == null) + conn.Settings.Database = username; + + timeout.CheckAndApply(conn); + conn.WriteStartupMessage(username); + await conn.Flush(async, cancellationToken); + + var cancellationRegistration = conn.StartCancellableOperation(cancellationToken, attemptPgCancellation: false); + try + { + await conn.Authenticate(username, timeout, async, cancellationToken); + } + catch (PostgresException e) + when (e.SqlState == PostgresErrorCodes.InvalidAuthorizationSpecification && + (sslMode == SslMode.Prefer && conn.IsSecure || sslMode == SslMode.Allow && !conn.IsSecure)) + { + cancellationRegistration.Dispose(); + Debug.Assert(!conn.IsBroken); + + conn.Cleanup(); + + // If Prefer was specified and we failed (with SSL), retry without SSL. + // If Allow was specified and we failed (without SSL), retry with SSL + await OpenCore( + conn, + sslMode == SslMode.Prefer ? SslMode.Disable : SslMode.Require, + timeout, + async, + cancellationToken, + isFirstAttempt: false); + + return; + } + + using var _ = cancellationRegistration; + + // We treat BackendKeyData as optional because some PostgreSQL-like database + // don't send it (CockroachDB, CrateDB) + var msg = await conn.ReadMessage(async); + if (msg.Code == BackendMessageCode.BackendKeyData) + { + var keyDataMsg = (BackendKeyDataMessage)msg; + conn.BackendProcessId = keyDataMsg.BackendProcessId; + conn._backendSecretKey = keyDataMsg.BackendSecretKey; + msg = await conn.ReadMessage(async); + } + + if (msg.Code != BackendMessageCode.ReadyForQuery) + throw new NpgsqlException($"Received backend message {msg.Code} while expecting ReadyForQuery. Please file a bug."); + + conn.State = ConnectorState.Ready; + } + } + + internal async ValueTask LoadDatabaseInfo(bool forceReload, NpgsqlTimeout timeout, bool async, + CancellationToken cancellationToken = default) + { + // The type loading below will need to send queries to the database, and that depends on a type mapper being set up (even if its + // empty). So we set up here, and then later inject the DatabaseInfo. + // For multiplexing connectors, the type mapper is the shared pool-wide one (since when validating/binding parameters on + // multiplexing there's no connector yet). However, in the very first multiplexing connection (bootstrap phase) we create + // a connector-specific mapper, which will later become shared pool-wide one. + TypeMapper = + Settings.Multiplexing && ((MultiplexingConnectorPool)_connectorSource).MultiplexingTypeMapper is { } multiplexingTypeMapper + ? multiplexingTypeMapper + : new ConnectorTypeMapper(this); + + var key = new NpgsqlDatabaseInfoCacheKey(Settings); + if (forceReload || !NpgsqlDatabaseInfo.Cache.TryGetValue(key, out var database)) + { + var hasSemaphore = async + ? await DatabaseInfoSemaphore.WaitAsync(timeout.CheckAndGetTimeLeft(), cancellationToken) + : DatabaseInfoSemaphore.Wait(timeout.CheckAndGetTimeLeft(), cancellationToken); + + // We've timed out - calling Check, to throw the correct exception + if (!hasSemaphore) + timeout.Check(); + + try + { + if (forceReload || !NpgsqlDatabaseInfo.Cache.TryGetValue(key, out database)) + { + NpgsqlDatabaseInfo.Cache[key] = database = await NpgsqlDatabaseInfo.Load(this, timeout, async); + } + } + finally + { + DatabaseInfoSemaphore.Release(); + } + } + + DatabaseInfo = database; + TypeMapper.DatabaseInfo = database; + } + + internal async ValueTask QueryClusterState( + NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken = default) + { + using var batch = CreateBatch(); + batch.BatchCommands.Add(new NpgsqlBatchCommand("select pg_is_in_recovery()")); + batch.BatchCommands.Add(new NpgsqlBatchCommand("SHOW default_transaction_read_only")); + batch.Timeout = (int)timeout.CheckAndGetTimeLeft().TotalSeconds; + + var reader = async ? await batch.ExecuteReaderAsync(cancellationToken) : batch.ExecuteReader(); + try + { + if (async) + { + await reader.ReadAsync(cancellationToken); + _isHotStandBy = reader.GetBoolean(0); + await reader.NextResultAsync(cancellationToken); + await reader.ReadAsync(cancellationToken); + } + else + { + reader.Read(); + _isHotStandBy = reader.GetBoolean(0); + reader.NextResult(); + reader.Read(); + } + + _isTransactionReadOnly = reader.GetString(0) != "off"; + + var clusterState = UpdateClusterState(); + Debug.Assert(clusterState.HasValue); + return clusterState.Value; + } + finally + { + if (async) + await reader.DisposeAsync(); + else + reader.Dispose(); + } + } + + void WriteStartupMessage(string username) + { + var startupParams = new Dictionary + { + ["user"] = username, + ["client_encoding"] = Settings.ClientEncoding ?? + PostgresEnvironment.ClientEncoding ?? + "UTF8", + ["database"] = Settings.Database! + }; + + if (Settings.ApplicationName?.Length > 0) + startupParams["application_name"] = Settings.ApplicationName; + + if (Settings.SearchPath?.Length > 0) + startupParams["search_path"] = Settings.SearchPath; + + var timezone = Settings.Timezone ?? PostgresEnvironment.TimeZone; + if (timezone != null) + startupParams["TimeZone"] = timezone; + + var options = Settings.Options ?? PostgresEnvironment.Options; + if (options?.Length > 0) + startupParams["options"] = options; + + switch (Settings.ReplicationMode) + { + case ReplicationMode.Logical: + startupParams["replication"] = "database"; + break; + case ReplicationMode.Physical: + startupParams["replication"] = "true"; + break; + } + + WriteStartup(startupParams); + } + + string GetUsername() + { + var username = Settings.Username; + if (username?.Length > 0) + return username; + + username = PostgresEnvironment.User; + if (username?.Length > 0) + return username; + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + username = KerberosUsernameProvider.GetUsername(Settings.IncludeRealm); + if (username?.Length > 0) + return username; + } + + username = Environment.UserName; + if (username?.Length > 0) + return username; + + throw new NpgsqlException("No username could be found, please specify one explicitly"); + } + + async Task RawOpen(SslMode sslMode, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken, bool isFirstAttempt = true) + { + try + { + if (async) + await ConnectAsync(timeout, cancellationToken); + else + Connect(timeout); + + _baseStream = new NetworkStream(_socket, true); + _stream = _baseStream; + + if (Settings.Encoding == "UTF8") + { + TextEncoding = PGUtil.UTF8Encoding; + RelaxedTextEncoding = PGUtil.RelaxedUTF8Encoding; + } + else + { + TextEncoding = Encoding.GetEncoding(Settings.Encoding, EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback); + RelaxedTextEncoding = Encoding.GetEncoding(Settings.Encoding, EncoderFallback.ReplacementFallback, DecoderFallback.ReplacementFallback); + } + + ReadBuffer = new NpgsqlReadBuffer(this, _stream, _socket, Settings.ReadBufferSize, TextEncoding, RelaxedTextEncoding); + WriteBuffer = new NpgsqlWriteBuffer(this, _stream, _socket, Settings.WriteBufferSize, TextEncoding); + + timeout.CheckAndApply(this); + + IsSecure = false; + + if (sslMode is SslMode.Prefer or SslMode.Require or SslMode.VerifyCA or SslMode.VerifyFull) + { + WriteSslRequest(); + await Flush(async, cancellationToken); + + await ReadBuffer.Ensure(1, async); + var response = (char)ReadBuffer.ReadByte(); + timeout.CheckAndApply(this); + + switch (response) + { + default: + throw new NpgsqlException($"Received unknown response {response} for SSLRequest (expecting S or N)"); + case 'N': + if (sslMode != SslMode.Prefer) + throw new NpgsqlException("SSL connection requested. No SSL enabled connection from this host is configured."); + break; + case 'S': + var clientCertificates = new X509Certificate2Collection(); + var certPath = Settings.SslCertificate ?? PostgresEnvironment.SslCert ?? PostgresEnvironment.SslCertDefault; + + if (certPath != null) + { + var password = Settings.SslPassword; + + if (Path.GetExtension(certPath).ToUpperInvariant() != ".PFX") + { +#if NET5_0_OR_GREATER + // It's PEM time + var keyPath = Settings.SslKey ?? PostgresEnvironment.SslKey ?? PostgresEnvironment.SslKeyDefault; + _certificate = string.IsNullOrEmpty(password) + ? X509Certificate2.CreateFromPemFile(certPath, keyPath) + : X509Certificate2.CreateFromEncryptedPemFile(certPath, password, keyPath); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // Windows crypto API has a bug with pem certs + // See #3650 + using var previousCert = _certificate; + _certificate = new X509Certificate2(_certificate.Export(X509ContentType.Pkcs12)); + } +#else + throw new NotSupportedException("PEM certificates are only supported with .NET 5 and higher"); +#endif + } + if (_certificate is null) + _certificate = new X509Certificate2(certPath, password); + clientCertificates.Add(_certificate); + } + + ProvideClientCertificatesCallback?.Invoke(clientCertificates); + + var checkCertificateRevocation = Settings.CheckCertificateRevocation; + + RemoteCertificateValidationCallback? certificateValidationCallback; + + if (UserCertificateValidationCallback is not null) + { + if (sslMode is SslMode.VerifyCA or SslMode.VerifyFull) + throw new ArgumentException(string.Format(NpgsqlStrings.CannotUseSslVerifyWithUserCallback, sslMode)); + + if (Settings.RootCertificate is not null) + throw new ArgumentException(NpgsqlStrings.CannotUseSslRootCertificateWithUserCallback); + + certificateValidationCallback = UserCertificateValidationCallback; + } + else if (sslMode is SslMode.Prefer or SslMode.Require) + { + if (isFirstAttempt && sslMode is SslMode.Require && !Settings.TrustServerCertificate) + throw new ArgumentException(NpgsqlStrings.CannotUseSslModeRequireWithoutTrustServerCertificate); + + certificateValidationCallback = SslTrustServerValidation; + checkCertificateRevocation = false; + } + else if ((Settings.RootCertificate ?? PostgresEnvironment.SslCertRoot ?? PostgresEnvironment.SslCertRootDefault) is + { } certRootPath) + { + certificateValidationCallback = SslRootValidation(certRootPath, sslMode == SslMode.VerifyFull); + } + else if (sslMode == SslMode.VerifyCA) + { + certificateValidationCallback = SslVerifyCAValidation; + } + else + { + Debug.Assert(sslMode == SslMode.VerifyFull); + certificateValidationCallback = SslVerifyFullValidation; + } + + timeout.CheckAndApply(this); + + try + { + var sslStream = new SslStream(_stream, leaveInnerStreamOpen: false, certificateValidationCallback); + + var sslProtocols = SslProtocols.None; + // On .NET Framework SslProtocols.None can be disabled, see #3718 +#if NETSTANDARD2_0 + sslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12; +#endif + + if (async) + await sslStream.AuthenticateAsClientAsync(Host, clientCertificates, + sslProtocols, checkCertificateRevocation); + else + sslStream.AuthenticateAsClient(Host, clientCertificates, + sslProtocols, checkCertificateRevocation); + + _stream = sslStream; + } + catch (Exception e) + { + throw new NpgsqlException("Exception while performing SSL handshake", e); + } + + ReadBuffer.Underlying = _stream; + WriteBuffer.Underlying = _stream; + IsSecure = true; + Log.Trace("SSL negotiation successful"); + break; + } + + if (ReadBuffer.ReadBytesLeft > 0) + throw new NpgsqlException("Additional unencrypted data received after SSL negotiation - this should never happen, and may be an indication of a man-in-the-middle attack."); + } + + Log.Trace($"Socket connected to {Host}:{Port}"); + } + catch + { + _certificate?.Dispose(); + _certificate = null; + + _stream?.Dispose(); + _stream = null!; + + _baseStream?.Dispose(); + _baseStream = null!; + + _socket?.Dispose(); + _socket = null!; + + throw; + } + } + + void Connect(NpgsqlTimeout timeout) + { + // Note that there aren't any timeout-able or cancellable DNS methods + var endpoints = NpgsqlConnectionStringBuilder.IsUnixSocket(Host, Port, out var socketPath) + ? new EndPoint[] { new UnixDomainSocketEndPoint(socketPath) } + : Dns.GetHostAddresses(Host).Select(a => new IPEndPoint(a, Port)).ToArray(); + timeout.Check(); + + // Give each endpoint an equal share of the remaining time + var perEndpointTimeout = -1; // Default to infinity + if (timeout.IsSet) + perEndpointTimeout = (int)(timeout.CheckAndGetTimeLeft().Ticks / endpoints.Length / 10); + + for (var i = 0; i < endpoints.Length; i++) + { + var endpoint = endpoints[i]; + Log.Trace($"Attempting to connect to {endpoint}"); + var protocolType = + endpoint.AddressFamily == AddressFamily.InterNetwork || + endpoint.AddressFamily == AddressFamily.InterNetworkV6 + ? ProtocolType.Tcp + : ProtocolType.IP; + var socket = new Socket(endpoint.AddressFamily, SocketType.Stream, protocolType) + { + Blocking = false + }; + + try + { + try + { + socket.Connect(endpoint); + } + catch (SocketException e) + { + if (e.SocketErrorCode != SocketError.WouldBlock) + throw; + } + var write = new List { socket }; + var error = new List { socket }; + Socket.Select(null, write, error, perEndpointTimeout); + var errorCode = (int)socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Error)!; + if (errorCode != 0) + throw new SocketException(errorCode); + if (!write.Any()) + throw new TimeoutException("Timeout during connection attempt"); + socket.Blocking = true; + SetSocketOptions(socket); + _socket = socket; + ConnectedEndPoint = endpoint; + return; + } + catch (Exception e) + { + try { socket.Dispose(); } + catch + { + // ignored + } + + Log.Trace($"Failed to connect to {endpoint}", e); + + if (i == endpoints.Length - 1) + throw new NpgsqlException($"Failed to connect to {endpoint}", e); + } + } + } + + async Task ConnectAsync(NpgsqlTimeout timeout, CancellationToken cancellationToken) + { + // Note that there aren't any timeout-able or cancellable DNS methods + var endpoints = NpgsqlConnectionStringBuilder.IsUnixSocket(Host, Port, out var socketPath) + ? new EndPoint[] { new UnixDomainSocketEndPoint(socketPath) } + : (await GetHostAddressesAsync(timeout, cancellationToken)) + .Select(a => new IPEndPoint(a, Port)).ToArray(); + + // Give each IP an equal share of the remaining time + var perIpTimespan = default(TimeSpan); + var perIpTimeout = timeout; + if (timeout.IsSet) + { + perIpTimespan = new TimeSpan(timeout.CheckAndGetTimeLeft().Ticks / endpoints.Length); + perIpTimeout = new NpgsqlTimeout(perIpTimespan); + } + + for (var i = 0; i < endpoints.Length; i++) + { + var endpoint = endpoints[i]; + Log.Trace($"Attempting to connect to {endpoint}"); + var protocolType = + endpoint.AddressFamily == AddressFamily.InterNetwork || + endpoint.AddressFamily == AddressFamily.InterNetworkV6 + ? ProtocolType.Tcp + : ProtocolType.IP; + var socket = new Socket(endpoint.AddressFamily, SocketType.Stream, protocolType); + try + { + await OpenSocketConnectionAsync(socket, endpoint, perIpTimeout, cancellationToken); + SetSocketOptions(socket); + _socket = socket; + ConnectedEndPoint = endpoint; + return; + } + catch (Exception e) + { + try + { + socket.Dispose(); + } + catch + { + // ignored + } + + cancellationToken.ThrowIfCancellationRequested(); + + if (e is OperationCanceledException) + e = new TimeoutException("Timeout during connection attempt"); + + Log.Trace($"Failed to connect to {endpoint}", e); + + if (i == endpoints.Length - 1) + throw new NpgsqlException($"Failed to connect to {endpoint}", e); + } + } + + Task GetHostAddressesAsync(NpgsqlTimeout timeout, CancellationToken cancellationToken) + { + // .NET 6.0 added cancellation support to GetHostAddressesAsync, which allows us to implement real + // cancellation and timeout. On older TFMs, we fake-cancel the operation, i.e. stop waiting + // and raise the exception, but the actual connection task is left running. + +#if NET6_0_OR_GREATER + var task = TaskExtensions.ExecuteWithTimeout( + ct => Dns.GetHostAddressesAsync(Host, ct), + timeout, cancellationToken); +#else + var task = Dns.GetHostAddressesAsync(Host); +#endif + + // As the cancellation support of GetHostAddressesAsync is not guaranteed on all platforms + // we apply the fake-cancel mechanism in all cases. + return task.WithCancellationAndTimeout(timeout, cancellationToken); + } + + static Task OpenSocketConnectionAsync(Socket socket, EndPoint endpoint, NpgsqlTimeout perIpTimeout, CancellationToken cancellationToken) + { + // .NET 5.0 added cancellation support to ConnectAsync, which allows us to implement real + // cancellation and timeout. On older TFMs, we fake-cancel the operation, i.e. stop waiting + // and raise the exception, but the actual connection task is left running. + +#if NET5_0_OR_GREATER + return TaskExtensions.ExecuteWithTimeout( + ct => socket.ConnectAsync(endpoint, ct).AsTask(), + perIpTimeout, cancellationToken); +#else + return socket.ConnectAsync(endpoint) + .WithCancellationAndTimeout(perIpTimeout, cancellationToken); +#endif + } + } + + void SetSocketOptions(Socket socket) + { + if (socket.AddressFamily == AddressFamily.InterNetwork || socket.AddressFamily == AddressFamily.InterNetworkV6) + socket.NoDelay = true; + if (Settings.SocketReceiveBufferSize > 0) + socket.ReceiveBufferSize = Settings.SocketReceiveBufferSize; + if (Settings.SocketSendBufferSize > 0) + socket.SendBufferSize = Settings.SocketSendBufferSize; + + if (Settings.TcpKeepAlive) + socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); + if (Settings.TcpKeepAliveInterval > 0 && Settings.TcpKeepAliveTime == 0) + throw new ArgumentException("If TcpKeepAliveInterval is defined, TcpKeepAliveTime must be defined as well"); + if (Settings.TcpKeepAliveTime > 0) + { + var timeSeconds = Settings.TcpKeepAliveTime; + var intervalSeconds = Settings.TcpKeepAliveInterval > 0 + ? Settings.TcpKeepAliveInterval + : Settings.TcpKeepAliveTime; + +#if NETSTANDARD2_0 || NETSTANDARD2_1 + var timeMilliseconds = timeSeconds * 1000; + var intervalMilliseconds = intervalSeconds * 1000; + + // For the following see https://msdn.microsoft.com/en-us/library/dd877220.aspx + var uintSize = Marshal.SizeOf(typeof(uint)); + var inOptionValues = new byte[uintSize * 3]; + BitConverter.GetBytes((uint)1).CopyTo(inOptionValues, 0); + BitConverter.GetBytes((uint)timeMilliseconds).CopyTo(inOptionValues, uintSize); + BitConverter.GetBytes((uint)intervalMilliseconds).CopyTo(inOptionValues, uintSize * 2); + var result = 0; + try + { + result = socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null); + } + catch (PlatformNotSupportedException) + { + throw new PlatformNotSupportedException("Setting TCP Keepalive Time and TCP Keepalive Interval is supported only on Windows, Mono and .NET Core 3.1+. " + + "TCP keepalives can still be used on other systems but are enabled via the TcpKeepAlive option or configured globally for the machine, see the relevant docs."); + } + + if (result != 0) + throw new NpgsqlException($"Got non-zero value when trying to set TCP keepalive: {result}"); +#else + socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); + socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, timeSeconds); + socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, intervalSeconds); +#endif + } + } + + #endregion + + #region I/O + + readonly ChannelReader? CommandsInFlightReader; + internal readonly ChannelWriter? CommandsInFlightWriter; + + internal volatile int CommandsInFlightCount; + + internal ManualResetValueTaskSource ReaderCompleted { get; } = + new() { RunContinuationsAsynchronously = true }; + + async Task MultiplexingReadLoop() + { + Debug.Assert(Settings.Multiplexing); + Debug.Assert(CommandsInFlightReader != null); + + NpgsqlCommand? command = null; + var commandsRead = 0; + + try + { + while (await CommandsInFlightReader.WaitToReadAsync()) + { + commandsRead = 0; + Debug.Assert(!InTransaction); + + while (CommandsInFlightReader.TryRead(out command)) + { + commandsRead++; + + await ReadBuffer.Ensure(5, true); + + // We have a resultset for the command - hand back control to the command (which will + // return it to the user) + command.TraceReceivedFirstResponse(); + ReaderCompleted.Reset(); + command.ExecutionCompletion.SetResult(this); + + // Now wait until that command's reader is disposed. Note that RunContinuationsAsynchronously is + // true, so that the user code calling NpgsqlDataReader.Dispose will not continue executing + // synchronously here. The prevents issues if the code after the next command's execution + // completion blocks. + await new ValueTask(ReaderCompleted, ReaderCompleted.Version); + Debug.Assert(!InTransaction); + } + + // Atomically update the commands in-flight counter, and check if it reached 0. If so, the + // connector is idle and can be returned. + // Note that this is racing with over-capacity writing, which can select any connector at any + // time (see MultiplexingWriteLoop), and we must make absolutely sure that if a connector is + // returned to the pool, it is *never* written to unless properly dequeued from the Idle channel. + if (Interlocked.Add(ref CommandsInFlightCount, -commandsRead) == 0) + { + // There's a race condition where the continuation of an asynchronous multiplexing write may not + // have executed yet, and the flush may still be in progress. We know all I/O has already + // been sent - because the reader has already consumed the entire resultset. So we wait until + // the connector's write lock has been released (long waiting will never occur here). + SpinWait.SpinUntil(() => MultiplexAsyncWritingLock == 0 || IsBroken); + + ResetReadBuffer(); + _connectorSource.Return(this); + } + } + + Log.Trace("Exiting multiplexing read loop", Id); + } + catch (Exception e) + { + Debug.Assert(IsBroken); + + // Decrement the commands already dequeued from the in-flight counter + Interlocked.Add(ref CommandsInFlightCount, -commandsRead); + + // When a connector is broken, the causing exception is stored on it. We fail commands with + // that exception - rather than the one thrown here - since the break may have happened during + // writing, and we want to bubble that one up. + + // Drain any pending in-flight commands and fail them. Note that some have only been written + // to the buffer, and not sent to the server. + command?.ExecutionCompletion.SetException(_breakReason!); + try + { + while (true) + { + var pendingCommand = await CommandsInFlightReader.ReadAsync(); + + // TODO: the exception we have here is sometimes just the result of the write loop breaking + // the connector, so it doesn't represent the actual root cause. + pendingCommand.ExecutionCompletion.SetException(_breakReason!); + } + } + catch (ChannelClosedException) + { + // All good, drained to the channel and failed all commands + } + + // "Return" the connector to the pool to for cleanup (e.g. update total connector count) + _connectorSource.Return(this); + + Log.Error("Exception in multiplexing read loop", e, Id); + } + + Debug.Assert(CommandsInFlightCount == 0); + } + + #endregion + + #region Frontend message processing + + /// + /// Prepends a message to be sent at the beginning of the next message chain. + /// + internal void PrependInternalMessage(byte[] rawMessage, int responseMessageCount) + { + PendingPrependedResponses += responseMessageCount; + + var t = WritePregenerated(rawMessage); + Debug.Assert(t.IsCompleted, "Could not fully write pregenerated message into the buffer"); + } + + #endregion + + #region Backend message processing + + internal IBackendMessage ReadMessage(DataRowLoadingMode dataRowLoadingMode = DataRowLoadingMode.NonSequential) + => ReadMessage(async: false, dataRowLoadingMode).GetAwaiter().GetResult(); + + internal ValueTask ReadMessage(bool async, DataRowLoadingMode dataRowLoadingMode = DataRowLoadingMode.NonSequential) + => ReadMessage(async, dataRowLoadingMode, readingNotifications: false)!; + + internal ValueTask ReadMessageWithNotifications(bool async) + => ReadMessage(async, DataRowLoadingMode.NonSequential, readingNotifications: true); + + internal ValueTask ReadMessage( + bool async, + DataRowLoadingMode dataRowLoadingMode, + bool readingNotifications) + { + if (PendingPrependedResponses > 0 || + dataRowLoadingMode != DataRowLoadingMode.NonSequential || + readingNotifications || + ReadBuffer.ReadBytesLeft < 5) + { + return ReadMessageLong(this, async, dataRowLoadingMode, readingNotifications); + } + + var messageCode = (BackendMessageCode)ReadBuffer.ReadByte(); + switch (messageCode) + { + case BackendMessageCode.NoticeResponse: + case BackendMessageCode.NotificationResponse: + case BackendMessageCode.ParameterStatus: + case BackendMessageCode.ErrorResponse: + ReadBuffer.ReadPosition--; + return ReadMessageLong(this, async, dataRowLoadingMode, readingNotifications: false); + case BackendMessageCode.ReadyForQuery: + break; + } + + PGUtil.ValidateBackendMessageCode(messageCode); + var len = ReadBuffer.ReadInt32() - 4; // Transmitted length includes itself + if (len > ReadBuffer.ReadBytesLeft) + { + ReadBuffer.ReadPosition -= 5; + return ReadMessageLong(this, async, dataRowLoadingMode, readingNotifications: false); + } + + return new ValueTask(ParseServerMessage(ReadBuffer, messageCode, len, false)); + + static async ValueTask ReadMessageLong( + NpgsqlConnector connector, + bool async, + DataRowLoadingMode dataRowLoadingMode, + bool readingNotifications, + bool isReadingPrependedMessage = false) + { + // First read the responses of any prepended messages. + if (connector.PendingPrependedResponses > 0 && !isReadingPrependedMessage) + { + try + { + // TODO: There could be room for optimization here, rather than the async call(s) + connector.ReadBuffer.Timeout = TimeSpan.FromMilliseconds(connector.InternalCommandTimeout); + for (; connector.PendingPrependedResponses > 0; connector.PendingPrependedResponses--) + await ReadMessageLong(connector, async, DataRowLoadingMode.Skip, readingNotifications: false, isReadingPrependedMessage: true); + // We've read all the prepended response. + // Allow cancellation to proceed. + connector.ReadingPrependedMessagesMRE.Set(); + } + catch (Exception e) + { + // Prepended queries should never fail. + // If they do, we're not even going to attempt to salvage the connector. + throw connector.Break(e); + } + } + + PostgresException? error = null; + + try + { + connector.ReadBuffer.Timeout = TimeSpan.FromMilliseconds(connector.UserTimeout); + + while (true) + { + await connector.ReadBuffer.Ensure(5, async, readingNotifications); + var messageCode = (BackendMessageCode)connector.ReadBuffer.ReadByte(); + PGUtil.ValidateBackendMessageCode(messageCode); + var len = connector.ReadBuffer.ReadInt32() - 4; // Transmitted length includes itself + + if ((messageCode == BackendMessageCode.DataRow && + dataRowLoadingMode != DataRowLoadingMode.NonSequential) || + messageCode == BackendMessageCode.CopyData) + { + if (dataRowLoadingMode == DataRowLoadingMode.Skip) + { + await connector.ReadBuffer.Skip(len, async); + continue; + } + } + else if (len > connector.ReadBuffer.ReadBytesLeft) + { + if (len > connector.ReadBuffer.Size) + { + var oversizeBuffer = connector.ReadBuffer.AllocateOversize(len); + + if (connector._origReadBuffer == null) + connector._origReadBuffer = connector.ReadBuffer; + else + connector.ReadBuffer.Dispose(); + + connector.ReadBuffer = oversizeBuffer; + } + + await connector.ReadBuffer.Ensure(len, async); + } + + var msg = connector.ParseServerMessage(connector.ReadBuffer, messageCode, len, isReadingPrependedMessage); + + switch (messageCode) + { + case BackendMessageCode.ErrorResponse: + Debug.Assert(msg == null); + + // An ErrorResponse is (almost) always followed by a ReadyForQuery. Save the error + // and throw it as an exception when the ReadyForQuery is received (next). + error = PostgresException.Load(connector.ReadBuffer, connector.Settings.IncludeErrorDetail); + + if (connector.State == ConnectorState.Connecting) + { + // During the startup/authentication phase, an ErrorResponse isn't followed by + // an RFQ. Instead, the server closes the connection immediately + throw error; + } + else if (PostgresErrorCodes.IsCriticalFailure(error, clusterError: false)) + { + // Consider the connection dead + throw connector.Break(error); + } + + continue; + + case BackendMessageCode.ReadyForQuery: + if (error != null) + { + NpgsqlEventSource.Log.CommandFailed(); + throw error; + } + + break; + + // Asynchronous messages which can come anytime, they have already been handled + // in ParseServerMessage. Read the next message. + case BackendMessageCode.NoticeResponse: + case BackendMessageCode.NotificationResponse: + case BackendMessageCode.ParameterStatus: + Debug.Assert(msg == null); + if (!readingNotifications) + continue; + return null; + } + + Debug.Assert(msg != null, "Message is null for code: " + messageCode); + return msg; + } + } + catch (PostgresException e) + { + // TODO: move it up the stack, like #3126 did (relevant for non-command-execution scenarios, like COPY) + if (connector.CurrentReader is null) + connector.EndUserAction(); + + if (e.SqlState == PostgresErrorCodes.QueryCanceled && connector.PostgresCancellationPerformed) + { + // The query could be canceled because of a user cancellation or a timeout - raise the proper exception. + // If _postgresCancellationPerformed is false, this is an unsolicited cancellation - + // just bubble up thePostgresException. + throw connector.UserCancellationRequested + ? new OperationCanceledException("Query was cancelled", e, connector.UserCancellationToken) + : new NpgsqlException("Exception while reading from stream", + new TimeoutException("Timeout during reading attempt")); + } + + throw; + } + catch (NpgsqlException) + { + // An ErrorResponse isn't followed by ReadyForQuery + if (error != null) + ExceptionDispatchInfo.Capture(error).Throw(); + throw; + } + } + } + + internal IBackendMessage? ParseServerMessage(NpgsqlReadBuffer buf, BackendMessageCode code, int len, bool isPrependedMessage) + { + switch (code) + { + case BackendMessageCode.RowDescription: + return _rowDescriptionMessage.Load(buf, TypeMapper); + case BackendMessageCode.DataRow: + return _dataRowMessage.Load(len); + case BackendMessageCode.CommandComplete: + return _commandCompleteMessage.Load(buf, len); + case BackendMessageCode.ReadyForQuery: + var rfq = _readyForQueryMessage.Load(buf); + if (!isPrependedMessage) + { + // Transaction status on prepended messages shouldn't be processed, because there may be prepended messages + // before the begin transaction message. In this case, they will contain transaction status Idle, which will + // clear our Pending transaction status. Only process transaction status on RFQ's from user-provided, non + // prepended messages. + ProcessNewTransactionStatus(rfq.TransactionStatusIndicator); + } + return rfq; + case BackendMessageCode.EmptyQueryResponse: + return EmptyQueryMessage.Instance; + case BackendMessageCode.ParseComplete: + return ParseCompleteMessage.Instance; + case BackendMessageCode.ParameterDescription: + return _parameterDescriptionMessage.Load(buf); + case BackendMessageCode.BindComplete: + return BindCompleteMessage.Instance; + case BackendMessageCode.NoData: + return NoDataMessage.Instance; + case BackendMessageCode.CloseComplete: + return CloseCompletedMessage.Instance; + case BackendMessageCode.ParameterStatus: + ReadParameterStatus(buf.GetNullTerminatedBytes(), buf.GetNullTerminatedBytes()); + return null; + case BackendMessageCode.NoticeResponse: + var notice = PostgresNotice.Load(buf, Settings.IncludeErrorDetail); + Log.Debug($"Received notice: {notice.MessageText}", Id); + Connection?.OnNotice(notice); + return null; + case BackendMessageCode.NotificationResponse: + Connection?.OnNotification(new NpgsqlNotificationEventArgs(buf)); + return null; + + case BackendMessageCode.AuthenticationRequest: + var authType = (AuthenticationRequestType)buf.ReadInt32(); + return authType switch + { + AuthenticationRequestType.AuthenticationOk => (AuthenticationRequestMessage)AuthenticationOkMessage.Instance, + AuthenticationRequestType.AuthenticationCleartextPassword => AuthenticationCleartextPasswordMessage.Instance, + AuthenticationRequestType.AuthenticationMD5Password => AuthenticationMD5PasswordMessage.Load(buf), + AuthenticationRequestType.AuthenticationGSS => AuthenticationGSSMessage.Instance, + AuthenticationRequestType.AuthenticationSSPI => AuthenticationSSPIMessage.Instance, + AuthenticationRequestType.AuthenticationGSSContinue => AuthenticationGSSContinueMessage.Load(buf, len), + AuthenticationRequestType.AuthenticationSASL => new AuthenticationSASLMessage(buf), + AuthenticationRequestType.AuthenticationSASLContinue => new AuthenticationSASLContinueMessage(buf, len - 4), + AuthenticationRequestType.AuthenticationSASLFinal => new AuthenticationSASLFinalMessage(buf, len - 4), + _ => throw new NotSupportedException($"Authentication method not supported (Received: {authType})") + }; + + case BackendMessageCode.BackendKeyData: + return new BackendKeyDataMessage(buf); + + case BackendMessageCode.CopyInResponse: + return (_copyInResponseMessage ??= new CopyInResponseMessage()).Load(ReadBuffer); + case BackendMessageCode.CopyOutResponse: + return (_copyOutResponseMessage ??= new CopyOutResponseMessage()).Load(ReadBuffer); + case BackendMessageCode.CopyData: + return (_copyDataMessage ??= new CopyDataMessage()).Load(len); + case BackendMessageCode.CopyBothResponse: + return (_copyBothResponseMessage ??= new CopyBothResponseMessage()).Load(ReadBuffer); + + case BackendMessageCode.CopyDone: + return CopyDoneMessage.Instance; + + case BackendMessageCode.PortalSuspended: + throw new NpgsqlException("Unimplemented message: " + code); + case BackendMessageCode.ErrorResponse: + return null; + + case BackendMessageCode.FunctionCallResponse: + // We don't use the obsolete function call protocol + throw new NpgsqlException("Unexpected backend message: " + code); + + default: + throw new InvalidOperationException($"Internal Npgsql bug: unexpected value {code} of enum {nameof(BackendMessageCode)}. Please file a bug."); + } + } + + /// + /// Reads backend messages and discards them, stopping only after a message of the given type has + /// been seen. Only a sync I/O version of this method exists - in async flows we inline the loop + /// rather than calling an additional async method, in order to avoid the overhead. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal IBackendMessage SkipUntil(BackendMessageCode stopAt) + { + Debug.Assert(stopAt != BackendMessageCode.DataRow, "Shouldn't be used for rows, doesn't know about sequential"); + + while (true) + { + var msg = ReadMessage(async: false, DataRowLoadingMode.Skip).GetAwaiter().GetResult()!; + Debug.Assert(!(msg is DataRowMessage)); + if (msg.Code == stopAt) + return msg; + } + } + + #endregion Backend message processing + + #region Transactions + + internal Task Rollback(bool async, CancellationToken cancellationToken = default) + { + Log.Debug("Rolling back transaction", Id); + return ExecuteInternalCommand(PregeneratedMessages.RollbackTransaction, async, cancellationToken); + } + + internal bool InTransaction + => TransactionStatus switch + { + TransactionStatus.Idle => false, + TransactionStatus.Pending => true, + TransactionStatus.InTransactionBlock => true, + TransactionStatus.InFailedTransactionBlock => true, + _ => throw new InvalidOperationException($"Internal Npgsql bug: unexpected value {TransactionStatus} of enum {nameof(TransactionStatus)}. Please file a bug.") + }; + + /// + /// Handles a new transaction indicator received on a ReadyForQuery message + /// + void ProcessNewTransactionStatus(TransactionStatus newStatus) + { + if (newStatus == TransactionStatus) + return; + + TransactionStatus = newStatus; + + switch (newStatus) + { + case TransactionStatus.Idle: + break; + case TransactionStatus.InTransactionBlock: + case TransactionStatus.InFailedTransactionBlock: + // In multiplexing mode, we can't support transaction in SQL: the connector must be removed from the + // writable connectors list, otherwise other commands may get written to it. So the user must tell us + // about the transaction via BeginTransaction. + if (Connection is null) + { + Debug.Assert(Settings.Multiplexing); + throw new NotSupportedException("In multiplexing mode, transactions must be started with BeginTransaction"); + } + break; + case TransactionStatus.Pending: + throw new Exception($"Internal Npgsql bug: invalid TransactionStatus {nameof(TransactionStatus.Pending)} received, should be frontend-only"); + default: + throw new InvalidOperationException( + $"Internal Npgsql bug: unexpected value {newStatus} of enum {nameof(TransactionStatus)}. Please file a bug."); + } + } + + internal void ClearTransaction(Exception? disposeReason = null) + { + Transaction?.DisposeImmediately(disposeReason); + TransactionStatus = TransactionStatus.Idle; + } + + #endregion + + #region SSL + + /// + /// Returns whether SSL is being used for the connection + /// + internal bool IsSecure { get; private set; } + + /// + /// Returns whether SCRAM-SHA256 is being user for the connection + /// + internal bool IsScram { get; private set; } + + /// + /// Returns whether SCRAM-SHA256-PLUS is being user for the connection + /// + internal bool IsScramPlus { get; private set; } + + static readonly RemoteCertificateValidationCallback SslVerifyFullValidation = + (sender, certificate, chain, sslPolicyErrors) + => sslPolicyErrors == SslPolicyErrors.None; + + static readonly RemoteCertificateValidationCallback SslVerifyCAValidation = + (sender, certificate, chain, sslPolicyErrors) + => sslPolicyErrors == SslPolicyErrors.None || sslPolicyErrors == SslPolicyErrors.RemoteCertificateNameMismatch; + + static readonly RemoteCertificateValidationCallback SslTrustServerValidation = + (sender, certificate, chain, sslPolicyErrors) + => true; + + static RemoteCertificateValidationCallback SslRootValidation(string certRootPath, bool verifyFull) => + (sender, certificate, chain, sslPolicyErrors) => + { + if (certificate is null || chain is null) + return false; + + // No errors here - no reason to check further + if (sslPolicyErrors == SslPolicyErrors.None) + return true; + + // That's VerifyCA check and the only error is name mismatch - no reason to check further + if (!verifyFull && sslPolicyErrors == SslPolicyErrors.RemoteCertificateNameMismatch) + return true; + + // That's VerifyFull check and we have name mismatch - no reason to check further + if (verifyFull && sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch)) + return false; + + var certs = new X509Certificate2Collection(); + +#if NET5_0_OR_GREATER + if (Path.GetExtension(certRootPath).ToUpperInvariant() != ".PFX") + certs.ImportFromPemFile(certRootPath); +#endif + + if (certs.Count == 0) + certs.Add(new X509Certificate2(certRootPath)); + +#if NET5_0_OR_GREATER + chain.ChainPolicy.CustomTrustStore.AddRange(certs); + chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; +#endif + + chain.ChainPolicy.ExtraStore.AddRange(certs); + + return chain.Build(certificate as X509Certificate2 ?? new X509Certificate2(certificate)); + }; + + #endregion SSL + + #region Cancel + + internal void ResetCancellation() + { + // If a cancellation is in progress, wait for it to "complete" before proceeding (#615) + lock (CancelLock) + { + if (PendingPrependedResponses > 0) + ReadingPrependedMessagesMRE.Reset(); + Debug.Assert(ReadingPrependedMessagesMRE.IsSet || PendingPrependedResponses > 0); + } + } + + internal void PerformUserCancellation() + { + var connection = Connection; + if (connection is null || connection.ConnectorBindingScope == ConnectorBindingScope.Reader || UserCancellationRequested) + return; + + // Take the lock first to make sure there is no concurrent Break. + // We should be safe to take it as Break only take it to set the state. + lock (SyncObj) + { + // The connector is dead, exit gracefully. + if (!IsConnected) + return; + // The connector is still alive, take the CancelLock before exiting SingleUseLock. + // If a break will happen after, it's going to wait for the cancellation to complete. + Monitor.Enter(CancelLock); + } + + try + { + // Wait before we've read all responses for the prepended queries + // as we can't gracefully handle their cancellation. + // Break makes sure that it's going to be set even if we fail while reading them. + + // We don't wait indefinitely to avoid deadlocks from synchronous CancellationToken.Register + // See #5032 + if (!ReadingPrependedMessagesMRE.Wait(0)) + return; + + _userCancellationRequested = true; + + if (AttemptPostgresCancellation && SupportsPostgresCancellation) + { + var cancellationTimeout = Settings.CancellationTimeout; + if (PerformPostgresCancellation() && cancellationTimeout >= 0) + { + if (cancellationTimeout > 0) + { + UserTimeout = cancellationTimeout; + ReadBuffer.Timeout = TimeSpan.FromMilliseconds(cancellationTimeout); + ReadBuffer.Cts.CancelAfter(cancellationTimeout); + } + + return; + } + } + + UserTimeout = -1; + ReadBuffer.Timeout = _cancelImmediatelyTimeout; + ReadBuffer.Cts.Cancel(); + } + finally + { + Monitor.Exit(CancelLock); + } + } + + /// + /// Creates another connector and sends a cancel request through it for this connector. This method never throws, but returns + /// whether the cancellation attempt failed. + /// + /// + /// + /// if the cancellation request was successfully delivered, or if it was skipped because a previous + /// request was already sent. if the cancellation request could not be delivered because of an exception + /// (the method logs internally). + /// + /// + /// This does not indicate whether the cancellation attempt was successful on the PostgreSQL side - only if the request was + /// delivered. + /// + /// + internal bool PerformPostgresCancellation() + { + Debug.Assert(BackendProcessId != 0, "PostgreSQL cancellation requested by the backend doesn't support it"); + + lock (CancelLock) + { + if (PostgresCancellationPerformed) + return true; + + Log.Debug("Sending cancellation...", Id); + PostgresCancellationPerformed = true; + + try + { + var cancelConnector = new NpgsqlConnector(this); + cancelConnector.DoCancelRequest(BackendProcessId, _backendSecretKey); + } + catch (Exception e) + { + var socketException = e.InnerException as SocketException; + if (socketException == null || socketException.SocketErrorCode != SocketError.ConnectionReset) + { + Log.Debug("Exception caught while attempting to cancel command", e, Id); + return false; + } + } + + return true; + } + } + + void DoCancelRequest(int backendProcessId, int backendSecretKey) + { + Debug.Assert(State == ConnectorState.Closed); + + try + { + RawOpen(Settings.SslMode, new NpgsqlTimeout(TimeSpan.FromSeconds(ConnectionTimeout)), false, CancellationToken.None) + .GetAwaiter().GetResult(); + WriteCancelRequest(backendProcessId, backendSecretKey); + Flush(); + + Debug.Assert(ReadBuffer.ReadBytesLeft == 0); + + // Now wait for the server to close the connection, better chance of the cancellation + // actually being delivered before we continue with the user's logic. + var count = _stream.Read(ReadBuffer.Buffer, 0, 1); + if (count > 0) + Log.Error("Received response after sending cancel request, shouldn't happen! First byte: " + ReadBuffer.Buffer[0]); + } + finally + { + FullCleanup(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal CancellationTokenRegistration StartCancellableOperation( + CancellationToken cancellationToken = default, + bool attemptPgCancellation = true) + { + _userCancellationRequested = PostgresCancellationPerformed = false; + UserCancellationToken = cancellationToken; + ReadBuffer.Cts.ResetCts(); + + AttemptPostgresCancellation = attemptPgCancellation; + return _cancellationTokenRegistration = + cancellationToken.Register(static c => ((NpgsqlConnector)c!).PerformUserCancellation(), this); + } + + /// + /// Starts a new cancellable operation within an ongoing user action. This should only be used if a single user + /// action spans several different actions which each has its own cancellation tokens. For example, a command + /// execution is a single user action, but spans ExecuteReaderQuery, NextResult, Read and so forth. + /// + /// + /// Only one level of nested operations is supported. It is an error to call this method if it has previously + /// been called, and the returned was not disposed. + /// + /// + /// The cancellation token provided by the user. Callbacks will be registered on this token for executing the + /// cancellation, and the token will be included in any thrown . + /// + /// + /// If , PostgreSQL cancellation will be attempted when the user requests cancellation or + /// a timeout occurs, followed by a client-side socket cancellation once + /// has elapsed. If , + /// PostgreSQL cancellation will be skipped and client-socket cancellation will occur immediately. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal CancellationTokenRegistration StartNestedCancellableOperation( + CancellationToken cancellationToken = default, + bool attemptPgCancellation = true) + { + UserCancellationToken = cancellationToken; + AttemptPostgresCancellation = attemptPgCancellation; + + return _cancellationTokenRegistration = + cancellationToken.Register(static c => ((NpgsqlConnector)c!).PerformUserCancellation(), this); + } + + #endregion Cancel + + #region Close / Reset + + /// + /// Closes ongoing operations, i.e. an open reader exists or a COPY operation still in progress, as + /// part of a connection close. + /// + internal async Task CloseOngoingOperations(bool async) + { + var reader = CurrentReader; + var copyOperation = CurrentCopyOperation; + + if (reader != null) + await reader.Close(connectionClosing: true, async, isDisposing: false); + else if (copyOperation != null) + { + // TODO: There's probably a race condition as the COPY operation may finish on its own during the next few lines + + // Note: we only want to cancel import operations, since in these cases cancel is safe. + // Export cancellations go through the PostgreSQL "asynchronous" cancel mechanism and are + // therefore vulnerable to the race condition in #615. + if (copyOperation is NpgsqlBinaryImporter || + copyOperation is NpgsqlCopyTextWriter || + copyOperation is NpgsqlRawCopyStream rawCopyStream && rawCopyStream.CanWrite) + { + try + { + if (async) + await copyOperation.CancelAsync(); + else + copyOperation.Cancel(); + } + catch (Exception e) + { + Log.Warn("Error while cancelling COPY on connector close", e, Id); + } + } + + try + { + if (async) + await copyOperation.DisposeAsync(); + else + copyOperation.Dispose(); + } + catch (Exception e) + { + Log.Warn("Error while disposing cancelled COPY on connector close", e, Id); + } + } + } + + // TODO in theory this should be async-optional, but the only I/O done here is the Terminate Flush, which is + // very unlikely to block (plus locking would need to be worked out) + internal void Close() + { + lock (SyncObj) + { + Log.Trace("Closing connector", Id); + + if (IsReady) + { + try + { + // At this point, there could be some prepended commands (like DISCARD ALL) + // which make no sense to send on connection close + // see https://github.com/npgsql/npgsql/issues/3592 + WriteBuffer.Clear(); + WriteTerminate(); + Flush(); + } + catch (Exception e) + { + Log.Error("Exception while closing connector", e, Id); + Debug.Assert(IsBroken); + } + } + + switch (State) + { + case ConnectorState.Broken: + case ConnectorState.Closed: + return; + } + + State = ConnectorState.Closed; + } + + FullCleanup(); + } + + internal bool TryRemovePendingEnlistedConnector(Transaction transaction) + => _connectorSource.TryRemovePendingEnlistedConnector(this, transaction); + + internal void Return() => _connectorSource.Return(this); + + /// + public void Dispose() => Close(); + + /// + /// Called when an unexpected message has been received during an action. Breaks the + /// connector and returns the appropriate message. + /// + internal Exception UnexpectedMessageReceived(BackendMessageCode received) + => throw Break(new Exception($"Received unexpected backend message {received}. Please file a bug.")); + + /// + /// Called when a connector becomes completely unusable, e.g. when an unexpected I/O exception is raised or when + /// we lose protocol sync. + /// Note that fatal errors during the Open phase do *not* pass through here. + /// + /// The exception that caused the break. + /// The exception given in for chaining calls. + internal Exception Break(Exception reason) + { + Debug.Assert(!IsClosed); + + Monitor.Enter(SyncObj); + + if (State == ConnectorState.Broken) + { + // We're already broken. + // Exit SingleUseLock to unblock other threads (like cancellation). + Monitor.Exit(SyncObj); + // Wait for the break to complete before going forward. + lock (CleanupLock) { } + return reason; + } + + try + { + // If we're broken while reading prepended messages + // the cancellation request might still be waiting on the MRE. + // Unblock it. + ReadingPrependedMessagesMRE.Set(); + + Log.Error("Breaking connector", reason, Id); + + // Note that we may be reading and writing from the same connector concurrently, so safely set + // the original reason for the break before actually closing the socket etc. + Interlocked.CompareExchange(ref _breakReason, reason, null); + State = ConnectorState.Broken; + // Take the CleanupLock while in SingleUseLock to make sure concurrent Break doesn't take it first. + Monitor.Enter(CleanupLock); + } + finally + { + // Unblock other threads (like cancellation) to proceed and exit gracefully. + Monitor.Exit(SyncObj); + } + + try + { + lock (CancelLock) + { + // Note we only set the cluster to offline and clear the pool if the connection is being broken (we're in this method), + // *and* the exception indicates that the PG cluster really is down; the latter includes any IO/timeout issue, + // but does not include e.g. authentication failure or timeouts with disabled cancellation. + if (reason is NpgsqlException { IsTransient: true } ne && + (ne.InnerException is not TimeoutException || Settings.CancellationTimeout != -1) || + reason is PostgresException pe && PostgresErrorCodes.IsCriticalFailure(pe)) + { + ClusterStateCache.UpdateClusterState(Host, Port, ClusterState.Offline, DateTime.UtcNow, + Settings.HostRecheckSecondsTranslated); + _connectorSource.Clear(); + } + + var connection = Connection; + + FullCleanup(); + + if (connection is not null) + { + var closeLockTaken = connection.TakeCloseLock(); + Debug.Assert(closeLockTaken); + if (Settings.ReplicationMode == ReplicationMode.Off) + { + Connection = null; + if (connection.ConnectorBindingScope != ConnectorBindingScope.None) + Return(); + connection.EnlistedTransaction = null; + connection.Connector = null; + connection.ConnectorBindingScope = ConnectorBindingScope.None; + } + + connection.FullState = ConnectionState.Broken; + connection.ReleaseCloseLock(); + } + } + + return reason; + } + finally + { + Monitor.Exit(CleanupLock); + } + } + + void FullCleanup() + { + lock (CleanupLock) + { + if (Settings.Multiplexing) + { + FlagAsNotWritableForMultiplexing(); + + // Note that in multiplexing, this could be called from the read loop, while the write loop is + // writing into the channel. To make sure this race condition isn't a problem, the channel currently + // isn't set up with SingleWriter (since at this point it doesn't do anything). + CommandsInFlightWriter!.Complete(); + + // The connector's read loop has a continuation to observe and log any exception coming out + // (see Open) + } + + Log.Trace("Cleaning up connector", Id); + Cleanup(); + + if (_isKeepAliveEnabled) + { + _userLock!.Dispose(); + _userLock = null; + _keepAliveTimer!.Change(Timeout.Infinite, Timeout.Infinite); + _keepAliveTimer.Dispose(); + } + } + } + + /// + /// Closes the socket and cleans up client-side resources associated with this connector. + /// + /// + /// This method doesn't actually perform any meaningful I/O, and therefore is sync-only. + /// + void Cleanup() + { + try + { + _stream?.Dispose(); + } + catch + { + // ignored + } + + if (CurrentReader != null) + { + CurrentReader.Command.State = CommandState.Idle; + try + { + // Note that this never actually blocks on I/O, since the stream is also closed + // (which is why we don't need to call CloseAsync) + CurrentReader.Close(); + } + catch + { + // ignored + } + CurrentReader = null; + } + + if (CurrentCopyOperation != null) + { + try + { + // Note that this never actually blocks on I/O, since the stream is also closed + // (which is why we don't need to call DisposeAsync) + CurrentCopyOperation.Dispose(); + } + catch + { + // ignored + } + CurrentCopyOperation = null; + } + + ClearTransaction(_breakReason); + + _stream = null!; + _baseStream = null!; + _origReadBuffer?.Dispose(); + _origReadBuffer = null; + ReadBuffer?.Dispose(); + ReadBuffer = null!; + WriteBuffer?.Dispose(); + WriteBuffer = null!; + Connection = null; + PostgresParameters.Clear(); + _currentCommand = null; + + if (_certificate is not null) + { + _certificate.Dispose(); + _certificate = null; + } + } + + void GenerateResetMessage() + { + var sb = new StringBuilder("SET SESSION AUTHORIZATION DEFAULT;RESET ALL;"); + _resetWithoutDeallocateResponseCount = 2; + if (DatabaseInfo.SupportsCloseAll) + { + sb.Append("CLOSE ALL;"); + _resetWithoutDeallocateResponseCount++; + } + if (DatabaseInfo.SupportsUnlisten) + { + sb.Append("UNLISTEN *;"); + _resetWithoutDeallocateResponseCount++; + } + if (DatabaseInfo.SupportsAdvisoryLocks) + { + sb.Append("SELECT pg_advisory_unlock_all();"); + _resetWithoutDeallocateResponseCount += 2; + } + if (DatabaseInfo.SupportsDiscardSequences) + { + sb.Append("DISCARD SEQUENCES;"); + _resetWithoutDeallocateResponseCount++; + } + if (DatabaseInfo.SupportsDiscardTemp) + { + sb.Append("DISCARD TEMP"); + _resetWithoutDeallocateResponseCount++; + } + + _resetWithoutDeallocateResponseCount++; // One ReadyForQuery at the end + + _resetWithoutDeallocateMessage = PregeneratedMessages.Generate(WriteBuffer, sb.ToString()); + } + + /// + /// Called when a pooled connection is closed, and its connector is returned to the pool. + /// Resets the connector back to its initial state, releasing server-side sources + /// (e.g. prepared statements), resetting parameters to their defaults, and resetting client-side + /// state + /// + internal async Task Reset(bool async) + { + bool endBindingScope; + + // We start user action in case a keeplive happens concurrently, or a concurrent user command (bug) + using (StartUserAction(attemptPgCancellation: false)) + { + // Our buffer may contain unsent prepended messages, so clear it out. + // In practice, this is (currently) only done when beginning a transaction or a transaction savepoint. + WriteBuffer.Clear(); + PendingPrependedResponses = 0; + + ResetReadBuffer(); + + Transaction?.UnbindIfNecessary(); + + // Must rollback transaction before sending DISCARD ALL + switch (TransactionStatus) + { + case TransactionStatus.Idle: + // There is an undisposed transaction on multiplexing connection + endBindingScope = Connection?.ConnectorBindingScope == ConnectorBindingScope.Transaction; + break; + case TransactionStatus.Pending: + // BeginTransaction() was called, but was left in the write buffer and not yet sent to server. + // Just clear the transaction state. + ProcessNewTransactionStatus(TransactionStatus.Idle); + ClearTransaction(); + endBindingScope = true; + break; + case TransactionStatus.InTransactionBlock: + case TransactionStatus.InFailedTransactionBlock: + await Rollback(async); + ClearTransaction(); + endBindingScope = true; + break; + default: + throw new InvalidOperationException($"Internal Npgsql bug: unexpected value {TransactionStatus} of enum {nameof(TransactionStatus)}. Please file a bug."); + } + + if (_sendResetOnClose) + { + if (PreparedStatementManager.NumPrepared > 0) + { + // We have prepared statements, so we can't reset the connection state with DISCARD ALL + // Note: the send buffer has been cleared above, and we assume all this will fit in it. + PrependInternalMessage(_resetWithoutDeallocateMessage!, _resetWithoutDeallocateResponseCount); + } + else + { + // There are no prepared statements. + // We simply send DISCARD ALL which is more efficient than sending the above messages separately + PrependInternalMessage(PregeneratedMessages.DiscardAll, 2); + } + } + + DataReader.UnbindIfNecessary(); + } + + if (endBindingScope) + { + // Connection is null if a connection enlisted in a TransactionScope was closed before the + // TransactionScope completed - the connector is still enlisted, but has no connection. + Connection?.EndBindingScope(ConnectorBindingScope.Transaction); + } + } + + /// + /// The connector may have allocated an oversize read buffer, to hold big rows in non-sequential reading. + /// This switches us back to the original one and returns the buffer to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void ResetReadBuffer() + { + if (_origReadBuffer != null) + { + Debug.Assert(_origReadBuffer.ReadBytesLeft == 0); + Debug.Assert(_origReadBuffer.ReadPosition == 0); + if (ReadBuffer.ReadBytesLeft > 0) + { + // There is still something in the buffer which we haven't read yet + // In most cases it's ParameterStatus which can be sent asynchronously + // If in some extreme case we have too much data left in the buffer to store in the original buffer + // we just leave the oversize buffer as is and will try again on next reset + if (ReadBuffer.ReadBytesLeft > _origReadBuffer.Size) + return; + + ReadBuffer.CopyTo(_origReadBuffer); + } + + ReadBuffer.Dispose(); + ReadBuffer = _origReadBuffer; + _origReadBuffer = null; + } + } + + internal void UnprepareAll() + { + ExecuteInternalCommand("DEALLOCATE ALL"); + PreparedStatementManager.ClearAll(); + } + + #endregion Close / Reset + + #region Locking + + internal UserAction StartUserAction(CancellationToken cancellationToken = default, bool attemptPgCancellation = true) + => StartUserAction(ConnectorState.Executing, command: null, cancellationToken, attemptPgCancellation); + + internal UserAction StartUserAction( + ConnectorState newState, + CancellationToken cancellationToken = default, + bool attemptPgCancellation = true) + => StartUserAction(newState, command: null, cancellationToken, attemptPgCancellation); + + /// + /// Starts a user action. This makes sure that another action isn't already in progress, handles synchronization with keepalive, + /// and sets up cancellation. + /// + /// The new state to be set when entering this user action. + /// + /// The that is starting execution - if an is + /// thrown, it will reference this. + /// + /// + /// The cancellation token provided by the user. Callbacks will be registered on this token for executing the cancellation, + /// and the token will be included in any thrown . + /// + /// + /// If , PostgreSQL cancellation will be attempted when the user requests cancellation or a timeout + /// occurs, followed by a client-side socket cancellation once has + /// elapsed. If , PostgreSQL cancellation will be skipped and client-socket cancellation will occur + /// immediately. + /// + internal UserAction StartUserAction( + ConnectorState newState, + NpgsqlCommand? command, + CancellationToken cancellationToken = default, + bool attemptPgCancellation = true) + { + // If keepalive is enabled, we must protect state transitions with a SemaphoreSlim + // (which itself must be protected by a lock, since its dispose isn't thread-safe). + // This will make the keepalive abort safely if a user query is in progress, and make + // the user query wait if a keepalive is in progress. + + // If keepalive isn't enabled, we don't use the semaphore and rely only on the connector's + // state (updated via Interlocked.Exchange) to detect concurrent use, on a best-effort basis. + if (!_isKeepAliveEnabled) + return DoStartUserAction(); + + lock (SyncObj) + { + if (!IsConnected) + { + throw IsBroken + ? new NpgsqlException("The connection was previously broken because of the following exception", _breakReason) + : new NpgsqlException("The connection is closed"); + } + + if (!_userLock!.Wait(0)) + { + var currentCommand = _currentCommand; + throw currentCommand == null + ? new NpgsqlOperationInProgressException(State) + : new NpgsqlOperationInProgressException(currentCommand); + } + + try + { + // Disable keepalive, it will be restarted at the end of the user action + _keepAliveTimer!.Change(Timeout.Infinite, Timeout.Infinite); + + // We now have both locks and are sure nothing else is running. + // Check that the connector is ready. + return DoStartUserAction(); + } + catch + { + _userLock.Release(); + throw; + } + } + + UserAction DoStartUserAction() + { + switch (State) + { + case ConnectorState.Ready: + break; + case ConnectorState.Closed: + case ConnectorState.Broken: + throw new InvalidOperationException("Connection is not open"); + case ConnectorState.Executing: + case ConnectorState.Fetching: + case ConnectorState.Waiting: + case ConnectorState.Replication: + case ConnectorState.Connecting: + case ConnectorState.Copy: + var currentCommand = _currentCommand; + throw currentCommand == null + ? new NpgsqlOperationInProgressException(State) + : new NpgsqlOperationInProgressException(currentCommand); + default: + throw new ArgumentOutOfRangeException(nameof(State), State, "Invalid connector state: " + State); + } + + Debug.Assert(IsReady); + + cancellationToken.ThrowIfCancellationRequested(); + + Log.Trace("Start user action", Id); + State = newState; + _currentCommand = command; + + StartCancellableOperation(cancellationToken, attemptPgCancellation); + + // We reset the UserTimeout for every user action, so it wouldn't leak from the previous query or action + // For example, we might have successfully cancelled the previous query (so the connection is not broken) + // But the next time, we call the Prepare, which doesn't set it's own timeout + UserTimeout = (command?.CommandTimeout ?? Settings.CommandTimeout) * 1000; + + return new UserAction(this); + } + } + + internal void EndUserAction() + { + Debug.Assert(CurrentReader == null); + + _cancellationTokenRegistration.Dispose(); + + if (_isKeepAliveEnabled) + { + lock (SyncObj) + { + if (IsReady || !IsConnected) + return; + + var keepAlive = Settings.KeepAlive * 1000; + _keepAliveTimer!.Change(keepAlive, keepAlive); + + Log.Trace("End user action", Id); + _currentCommand = null; + _userLock!.Release(); + State = ConnectorState.Ready; + } + } + else + { + if (IsReady || !IsConnected) + return; + + Log.Trace("End user action", Id); + _currentCommand = null; + State = ConnectorState.Ready; + } + } + + /// + /// An IDisposable wrapper around . + /// + internal readonly struct UserAction : IDisposable + { + readonly NpgsqlConnector _connector; + internal UserAction(NpgsqlConnector connector) => _connector = connector; + public void Dispose() => _connector.EndUserAction(); + } + + #endregion + + #region Keepalive + +#pragma warning disable CA1801 // Review unused parameters + void PerformKeepAlive(object? state) + { + Debug.Assert(_isKeepAliveEnabled); + + // SemaphoreSlim.Dispose() isn't thread-safe - it may be in progress so we shouldn't try to wait on it; + // we need a standard lock to protect it. + if (!Monitor.TryEnter(SyncObj)) + return; + + try + { + // There may already be a user action, or the connector may be closed etc. + if (!IsReady) + return; + + Log.Trace("Performing keepalive", Id); + AttemptPostgresCancellation = false; + var timeout = InternalCommandTimeout; + WriteBuffer.Timeout = TimeSpan.FromSeconds(timeout); + UserTimeout = timeout; + WriteSync(async: false); + Flush(); + SkipUntil(BackendMessageCode.ReadyForQuery); + Log.Trace("Performed keepalive", Id); + } + catch (Exception e) + { + Log.Error("Keepalive failure", e, Id); + try + { + Break(new NpgsqlException("Exception while sending a keepalive", e)); + } + catch (Exception e2) + { + Log.Error("Further exception while breaking connector on keepalive failure", e2, Id); + } + } + finally + { + Monitor.Exit(SyncObj); + } + } +#pragma warning restore CA1801 // Review unused parameters + + #endregion + + #region Wait + + internal async Task Wait(bool async, int timeout, CancellationToken cancellationToken = default) + { + using var _ = StartUserAction(ConnectorState.Waiting, cancellationToken: cancellationToken, attemptPgCancellation: false); + + // We may have prepended messages in the connection's write buffer - these need to be flushed now. + await Flush(async, cancellationToken); + + var keepaliveMs = Settings.KeepAlive * 1000; + while (true) + { + cancellationToken.ThrowIfCancellationRequested(); + + var timeoutForKeepalive = _isKeepAliveEnabled && (timeout <= 0 || keepaliveMs < timeout); + UserTimeout = timeoutForKeepalive ? keepaliveMs : timeout; + try + { + var msg = await ReadMessageWithNotifications(async); + if (msg != null) + { + throw Break( + new NpgsqlException($"Received unexpected message of type {msg.Code} while waiting")); + } + return true; + } + catch (NpgsqlException e) when (e.InnerException is TimeoutException) + { + if (!timeoutForKeepalive) // We really timed out + return false; + } + + // Time for a keepalive + var keepaliveTime = Stopwatch.StartNew(); + await WriteSync(async, cancellationToken); + await Flush(async, cancellationToken); + + var receivedNotification = false; + var expectedMessageCode = BackendMessageCode.RowDescription; + + while (true) + { + IBackendMessage? msg; + + try + { + msg = await ReadMessageWithNotifications(async); + } + catch (Exception e) when (e is OperationCanceledException || e is NpgsqlException npgEx && npgEx.InnerException is TimeoutException) + { + // We're somewhere in the middle of a reading keepalive messages + // Breaking the connection, as we've lost protocol sync + throw Break(e); + } + + if (msg == null) + { + receivedNotification = true; + continue; + } + + if (msg.Code != BackendMessageCode.ReadyForQuery) + throw new NpgsqlException($"Received unexpected message of type {msg.Code} while expecting {expectedMessageCode} as part of keepalive"); + + Log.Trace("Performed keepalive", Id); + + if (receivedNotification) + return true; // Notification was received during the keepalive + cancellationToken.ThrowIfCancellationRequested(); + break; + } + + if (timeout > 0) + timeout -= (keepaliveMs + (int)keepaliveTime.ElapsedMilliseconds); + } + } + + #endregion + + #region Supported features and PostgreSQL settings + + internal bool UseConformingStrings { get; private set; } + + /// + /// The connection's timezone as reported by PostgreSQL, in the IANA/Olson database format. + /// + internal string Timezone { get; private set; } = default!; + + bool? _isTransactionReadOnly; + + bool? _isHotStandBy; + + #endregion Supported features and PostgreSQL settings + + #region Execute internal command + + internal void ExecuteInternalCommand(string query) + => ExecuteInternalCommand(query, false).GetAwaiter().GetResult(); + + internal async Task ExecuteInternalCommand(string query, bool async, CancellationToken cancellationToken = default) + { + Log.Trace($"Executing internal command: {query}", Id); + + await WriteQuery(query, async, cancellationToken); + await Flush(async, cancellationToken); + Expect(await ReadMessage(async), this); + Expect(await ReadMessage(async), this); + } + + internal async Task ExecuteInternalCommand(byte[] data, bool async, CancellationToken cancellationToken = default) + { + Debug.Assert(State != ConnectorState.Ready, "Forgot to start a user action..."); + + Log.Trace("Executing internal pregenerated command", Id); + + await WritePregenerated(data, async, cancellationToken); + await Flush(async, cancellationToken); + Expect(await ReadMessage(async), this); + Expect(await ReadMessage(async), this); + } + + #endregion + + #region Misc + + /// + /// Creates and returns a object associated with the . + /// + /// The text of the query. + /// A object. + public NpgsqlCommand CreateCommand(string? cmdText = null) => new(cmdText, this); + + /// + /// Creates and returns a object associated with the . + /// + /// A object. + public NpgsqlBatch CreateBatch() => new NpgsqlBatch(this); + + void ReadParameterStatus(ReadOnlySpan incomingName, ReadOnlySpan incomingValue) + { + byte[] rawName; + byte[] rawValue; + + for (var i = 0; i < _rawParameters.Count; i++) + { + (var currentName, var currentValue) = _rawParameters[i]; + if (incomingName.SequenceEqual(currentName)) + { + if (incomingValue.SequenceEqual(currentValue)) + return; + + rawName = currentName; + rawValue = incomingValue.ToArray(); + _rawParameters[i] = (rawName, rawValue); + + goto ProcessParameter; + } + } + + rawName = incomingName.ToArray(); + rawValue = incomingValue.ToArray(); + _rawParameters.Add((rawName, rawValue)); + + ProcessParameter: + var name = TextEncoding.GetString(rawName); + var value = TextEncoding.GetString(rawValue); + + PostgresParameters[name] = value; + + switch (name) + { + case "standard_conforming_strings": + if (value != "on" && Settings.Multiplexing) + throw Break(new NotSupportedException("standard_conforming_strings must be on with multiplexing")); + UseConformingStrings = value == "on"; + return; + + case "TimeZone": + Timezone = value; + return; + + case "default_transaction_read_only": + _isTransactionReadOnly = value == "on"; + UpdateClusterState(); + return; + + case "in_hot_standby": + _isHotStandBy = value == "on"; + UpdateClusterState(); + return; + } + } + + ClusterState? UpdateClusterState() + { + if (_isTransactionReadOnly.HasValue && _isHotStandBy.HasValue) + { + var state = _isHotStandBy.Value + ? ClusterState.Standby + : _isTransactionReadOnly.Value + ? ClusterState.PrimaryReadOnly + : ClusterState.PrimaryReadWrite; + return ClusterStateCache.UpdateClusterState(Settings.Host!, Settings.Port, state, DateTime.UtcNow, + Settings.HostRecheckSecondsTranslated); + } + + return null; + } + + #endregion Misc +} + +#region Enums + +/// +/// Expresses the exact state of a connector. +/// +enum ConnectorState +{ + /// + /// The connector has either not yet been opened or has been closed. + /// + Closed, + + /// + /// The connector is currently connecting to a PostgreSQL server. + /// + Connecting, + + /// + /// The connector is connected and may be used to send a new query. + /// + Ready, + + /// + /// The connector is waiting for a response to a query which has been sent to the server. + /// + Executing, + + /// + /// The connector is currently fetching and processing query results. + /// + Fetching, + + /// + /// The connector is currently waiting for asynchronous notifications to arrive. + /// + Waiting, + + /// + /// The connection was broken because an unexpected error occurred which left it in an unknown state. + /// This state isn't implemented yet. + /// + Broken, + + /// + /// The connector is engaged in a COPY operation. + /// + Copy, + + /// + /// The connector is engaged in streaming replication. + /// + Replication, +} + +#pragma warning disable CA1717 +enum TransactionStatus : byte +#pragma warning restore CA1717 +{ + /// + /// Currently not in a transaction block + /// + Idle = (byte)'I', + + /// + /// Currently in a transaction block + /// + InTransactionBlock = (byte)'T', + + /// + /// Currently in a failed transaction block (queries will be rejected until block is ended) + /// + InFailedTransactionBlock = (byte)'E', + + /// + /// A new transaction has been requested but not yet transmitted to the backend. It will be transmitted + /// prepended to the next query. + /// This is a client-side state option only, and is never transmitted from the backend. + /// + Pending = byte.MaxValue, +} + +/// +/// Specifies how to load/parse DataRow messages as they're received from the backend. +/// +internal enum DataRowLoadingMode +{ + /// + /// Load DataRows in non-sequential mode + /// + NonSequential, + + /// + /// Load DataRows in sequential mode + /// + Sequential, + + /// + /// Skip DataRow messages altogether + /// + Skip +} + +#endregion diff --git a/LibExternal/Npgsql/Internal/NpgsqlDatabaseInfo.cs b/LibExternal/Npgsql/Internal/NpgsqlDatabaseInfo.cs new file mode 100644 index 0000000..b501cd5 --- /dev/null +++ b/LibExternal/Npgsql/Internal/NpgsqlDatabaseInfo.cs @@ -0,0 +1,337 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading.Tasks; +using Npgsql.PostgresTypes; +using Npgsql.Util; + +namespace Npgsql.Internal; + +/// +/// Base class for implementations which provide information about PostgreSQL and PostgreSQL-like databases +/// (e.g. type definitions, capabilities...). +/// +public abstract class NpgsqlDatabaseInfo +{ + #region Fields + + internal static readonly ConcurrentDictionary Cache + = new(); + + static volatile INpgsqlDatabaseInfoFactory[] Factories = new INpgsqlDatabaseInfoFactory[] + { + new PostgresMinimalDatabaseInfoFactory(), + new PostgresDatabaseInfoFactory() + }; + + #endregion Fields + + #region General database info + + /// + /// The hostname of IP address of the database. + /// + public string Host { get; } + + /// + /// The TCP port of the database. + /// + public int Port { get; } + + /// + /// The database name. + /// + public string Name { get; } + + /// + /// The version of the PostgreSQL database we're connected to, as reported in the "server_version" parameter. + /// Exposed via . + /// + public Version Version { get; } + + /// + /// The PostgreSQL version string as returned by the server_version option. Populated during loading. + /// + public string ServerVersion { get; } + + #endregion General database info + + #region Supported capabilities and features + + /// + /// Whether the backend supports range types. + /// + public virtual bool SupportsRangeTypes => Version.IsGreaterOrEqual(9, 2); + + /// + /// Whether the backend supports multirange types. + /// + public virtual bool SupportsMultirangeTypes => Version.IsGreaterOrEqual(14); + + /// + /// Whether the backend supports enum types. + /// + public virtual bool SupportsEnumTypes => Version.IsGreaterOrEqual(8, 3); + + /// + /// Whether the backend supports the CLOSE ALL statement. + /// + public virtual bool SupportsCloseAll => Version.IsGreaterOrEqual(8, 3); + + /// + /// Whether the backend supports advisory locks. + /// + public virtual bool SupportsAdvisoryLocks => Version.IsGreaterOrEqual(8, 2); + + /// + /// Whether the backend supports the DISCARD SEQUENCES statement. + /// + public virtual bool SupportsDiscardSequences => Version.IsGreaterOrEqual(9, 4); + + /// + /// Whether the backend supports the UNLISTEN statement. + /// + public virtual bool SupportsUnlisten => Version.IsGreaterOrEqual(6, 4); // overridden by PostgresDatabase + + /// + /// Whether the backend supports the DISCARD TEMP statement. + /// + public virtual bool SupportsDiscardTemp => Version.IsGreaterOrEqual(8, 3); + + /// + /// Whether the backend supports the DISCARD statement. + /// + public virtual bool SupportsDiscard => Version.IsGreaterOrEqual(8, 3); + + /// + /// Reports whether the backend uses the newer integer timestamp representation. + /// + public virtual bool HasIntegerDateTimes { get; protected set; } = true; + + /// + /// Whether the database supports transactions. + /// + public virtual bool SupportsTransactions { get; protected set; } = true; + + #endregion Supported capabilities and features + + #region Types + + readonly List _baseTypesMutable = new(); + readonly List _arrayTypesMutable = new(); + readonly List _rangeTypesMutable = new(); + readonly List _multirangeTypesMutable = new(); + readonly List _enumTypesMutable = new(); + readonly List _compositeTypesMutable = new(); + readonly List _domainTypesMutable = new(); + + internal IReadOnlyList BaseTypes => _baseTypesMutable; + internal IReadOnlyList ArrayTypes => _arrayTypesMutable; + internal IReadOnlyList RangeTypes => _rangeTypesMutable; + internal IReadOnlyList MultirangeTypes => _multirangeTypesMutable; + internal IReadOnlyList EnumTypes => _enumTypesMutable; + internal IReadOnlyList CompositeTypes => _compositeTypesMutable; + internal IReadOnlyList DomainTypes => _domainTypesMutable; + + /// + /// Indexes backend types by their type OID. + /// + internal Dictionary ByOID { get; } = new(); + + /// + /// Indexes backend types by their PostgreSQL name, including namespace (e.g. pg_catalog.int4). + /// Only used for enums and composites. + /// + internal Dictionary ByFullName { get; } = new(); + + /// + /// Indexes backend types by their PostgreSQL name, not including namespace. + /// If more than one type exists with the same name (i.e. in different namespaces) this + /// table will contain an entry with a null value. + /// Only used for enums and composites. + /// + internal Dictionary ByName { get; } = new(); + + /// + /// Initializes the instance of . + /// + protected NpgsqlDatabaseInfo(string host, int port, string databaseName, Version version) + : this(host, port, databaseName, version, version.ToString()) + { } + + /// + /// Initializes the instance of . + /// + protected NpgsqlDatabaseInfo(string host, int port, string databaseName, Version version, string serverVersion) + { + Host = host; + Port = port; + Name = databaseName; + Version = version; + ServerVersion = serverVersion; + } + + private protected NpgsqlDatabaseInfo(string host, int port, string databaseName, string serverVersion) + { + Host = host; + Port = port; + Name = databaseName; + ServerVersion = serverVersion; + Version = ParseServerVersion(serverVersion); + } + + public PostgresType GetPostgresTypeByName(string pgName) + => TryGetPostgresTypeByName(pgName, out var pgType) + ? pgType + : throw new ArgumentException($"A PostgreSQL type with the name {pgName} was not found in the database"); + + public bool TryGetPostgresTypeByName(string pgName, [NotNullWhen(true)] out PostgresType? pgType) + { + // Full type name with namespace + if (pgName.IndexOf('.') > -1) + { + if (ByFullName.TryGetValue(pgName, out pgType)) + return true; + } + // No dot, partial type name + else if (ByName.TryGetValue(pgName, out pgType)) + { + if (pgType is not null) + return true; + + // If the name was found but the value is null, that means that there are + // two db types with the same name (different schemas). + // Try to fall back to pg_catalog, otherwise fail. + if (ByFullName.TryGetValue($"pg_catalog.{pgName}", out pgType)) + return true; + + var ambiguousTypes = ByFullName.Keys.Where(n => n.EndsWith($".{pgName}", StringComparison.Ordinal)); + throw new ArgumentException($"More than one PostgreSQL type was found with the name {pgName}, " + + $"please specify a full name including schema: {string.Join(", ", ambiguousTypes)}"); + } + + return false; + } + + internal void ProcessTypes() + { + foreach (var type in GetTypes()) + { + ByOID[type.OID] = type; + ByFullName[type.FullName] = type; + // If more than one type exists with the same partial name, we place a null value. + // This allows us to detect this case later and force the user to use full names only. + ByName[type.Name] = ByName.ContainsKey(type.Name) + ? null + : type; + + switch (type) + { + case PostgresBaseType baseType: + _baseTypesMutable.Add(baseType); + continue; + case PostgresArrayType arrayType: + _arrayTypesMutable.Add(arrayType); + continue; + case PostgresRangeType rangeType: + _rangeTypesMutable.Add(rangeType); + continue; + case PostgresMultirangeType multirangeType: + _multirangeTypesMutable.Add(multirangeType); + continue; + case PostgresEnumType enumType: + _enumTypesMutable.Add(enumType); + continue; + case PostgresCompositeType compositeType: + _compositeTypesMutable.Add(compositeType); + continue; + case PostgresDomainType domainType: + _domainTypesMutable.Add(domainType); + continue; + default: + throw new ArgumentOutOfRangeException(); + } + } + } + + /// + /// Provides all PostgreSQL types detected in this database. + /// + /// + protected abstract IEnumerable GetTypes(); + + #endregion Types + + #region Misc + + /// + /// Parses a PostgreSQL server version (e.g. 10.1, 9.6.3) and returns a CLR Version. + /// + protected static Version ParseServerVersion(string value) + { + var versionString = value.TrimStart(); + for (var idx = 0; idx != versionString.Length; ++idx) + { + var c = versionString[idx]; + if (!char.IsDigit(c) && c != '.') + { + versionString = versionString.Substring(0, idx); + break; + } + } + if (!versionString.Contains(".")) + versionString += ".0"; + return new Version(versionString); + } + + #endregion Misc + + #region Factory management + + /// + /// Registers a new database info factory, which is used to load information about databases. + /// + public static void RegisterFactory(INpgsqlDatabaseInfoFactory factory) + { + if (factory == null) + throw new ArgumentNullException(nameof(factory)); + + var factories = new INpgsqlDatabaseInfoFactory[Factories.Length + 1]; + factories[0] = factory; + Array.Copy(Factories, 0, factories, 1, Factories.Length); + Factories = factories; + + Cache.Clear(); + } + + internal static async Task Load(NpgsqlConnector conn, NpgsqlTimeout timeout, bool async) + { + foreach (var factory in Factories) + { + var dbInfo = await factory.Load(conn, timeout, async); + if (dbInfo != null) + { + dbInfo.ProcessTypes(); + return dbInfo; + } + } + + // Should never be here + throw new NpgsqlException("No DatabaseInfoFactory could be found for this connection"); + } + + // For tests + internal static void ResetFactories() + { + Factories = new INpgsqlDatabaseInfoFactory[] + { + new PostgresMinimalDatabaseInfoFactory(), + new PostgresDatabaseInfoFactory() + }; + Cache.Clear(); + } + + #endregion Factory management +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/NpgsqlReadBuffer.Stream.cs b/LibExternal/Npgsql/Internal/NpgsqlReadBuffer.Stream.cs new file mode 100644 index 0000000..b13c33f --- /dev/null +++ b/LibExternal/Npgsql/Internal/NpgsqlReadBuffer.Stream.cs @@ -0,0 +1,237 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Npgsql.Internal; + +public sealed partial class NpgsqlReadBuffer +{ + internal sealed class ColumnStream : Stream + { + readonly NpgsqlConnector _connector; + readonly NpgsqlReadBuffer _buf; + int _start, _len, _read; + bool _canSeek; + bool _startCancellableOperations; + internal bool IsDisposed { get; private set; } + + internal ColumnStream(NpgsqlConnector connector, bool startCancellableOperations = true) + { + _connector = connector; + _buf = connector.ReadBuffer; + _startCancellableOperations = startCancellableOperations; + IsDisposed = true; + } + + internal void Init(int len, bool canSeek) + { + Debug.Assert(!canSeek || _buf.ReadBytesLeft >= len, + "Seekable stream constructed but not all data is in buffer (sequential)"); + _start = _buf.ReadPosition; + _len = len; + _read = 0; + _canSeek = canSeek; + IsDisposed = false; + } + + public override bool CanRead => true; + + public override bool CanWrite => false; + + public override bool CanSeek => _canSeek; + + public override long Length + { + get + { + CheckDisposed(); + return _len; + } + } + + public override void SetLength(long value) + => throw new NotSupportedException(); + + public override long Position + { + get + { + CheckDisposed(); + return _read; + } + set + { + if (value < 0) + throw new ArgumentOutOfRangeException(nameof(value), "Non - negative number required."); + Seek(value, SeekOrigin.Begin); + } + } + + public override long Seek(long offset, SeekOrigin origin) + { + CheckDisposed(); + + if (!_canSeek) + throw new NotSupportedException(); + if (offset > int.MaxValue) + throw new ArgumentOutOfRangeException(nameof(offset), "Stream length must be non-negative and less than 2^31 - 1 - origin."); + + const string seekBeforeBegin = "An attempt was made to move the position before the beginning of the stream."; + + switch (origin) + { + case SeekOrigin.Begin: + { + var tempPosition = unchecked(_start + (int)offset); + if (offset < 0 || tempPosition < _start) + throw new IOException(seekBeforeBegin); + _buf.ReadPosition = tempPosition; + _read = (int)offset; + return _read; + } + case SeekOrigin.Current: + { + var tempPosition = unchecked(_buf.ReadPosition + (int)offset); + if (unchecked(_buf.ReadPosition + offset) < _start || tempPosition < _start) + throw new IOException(seekBeforeBegin); + _buf.ReadPosition = tempPosition; + _read += (int)offset; + return _read; + } + case SeekOrigin.End: + { + var tempPosition = unchecked(_start + _len + (int)offset); + if (unchecked(_start + _len + offset) < _start || tempPosition < _start) + throw new IOException(seekBeforeBegin); + _buf.ReadPosition = tempPosition; + _read = _len + (int)offset; + return _read; + } + default: + throw new ArgumentOutOfRangeException(nameof(origin), "Invalid seek origin."); + } + } + + public override void Flush() + => CheckDisposed(); + + public override Task FlushAsync(CancellationToken cancellationToken) + { + CheckDisposed(); + return cancellationToken.IsCancellationRequested + ? Task.FromCanceled(cancellationToken) : Task.CompletedTask; + } + + public override int Read(byte[] buffer, int offset, int count) + { + ValidateArguments(buffer, offset, count); + return Read(new Span(buffer, offset, count)); + } + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + ValidateArguments(buffer, offset, count); + + using (NoSynchronizationContextScope.Enter()) + return ReadAsync(new Memory(buffer, offset, count), cancellationToken).AsTask(); + } + +#if NETSTANDARD2_0 + public int Read(Span span) +#else + public override int Read(Span span) +#endif + { + CheckDisposed(); + + var count = Math.Min(span.Length, _len - _read); + + if (count == 0) + return 0; + + var read = _buf.Read(span.Slice(0, count)); + _read += read; + + return read; + } + +#if NETSTANDARD2_0 + public ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) +#else + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) +#endif + { + CheckDisposed(); + + var count = Math.Min(buffer.Length, _len - _read); + + if (count == 0) + return new ValueTask(0); + + using (NoSynchronizationContextScope.Enter()) + return ReadLong(this, buffer.Slice(0, count), cancellationToken); + + static async ValueTask ReadLong(ColumnStream stream, Memory buffer, CancellationToken cancellationToken = default) + { + using var registration = stream._startCancellableOperations + ? stream._connector.StartNestedCancellableOperation(cancellationToken, attemptPgCancellation: false) + : default; + var read = await stream._buf.ReadAsync(buffer, cancellationToken); + stream._read += read; + return read; + } + } + + public override void Write(byte[] buffer, int offset, int count) + => throw new NotSupportedException(); + + void CheckDisposed() + { + if (IsDisposed) + throw new ObjectDisposedException(null); + } + + protected override void Dispose(bool disposing) + => DisposeAsync(disposing, async: false).GetAwaiter().GetResult(); + +#if NETSTANDARD2_0 + public ValueTask DisposeAsync() +#else + public override ValueTask DisposeAsync() +#endif + { + using (NoSynchronizationContextScope.Enter()) + return DisposeAsync(disposing: true, async: true); + } + + async ValueTask DisposeAsync(bool disposing, bool async) + { + if (IsDisposed || !disposing) + return; + + var leftToSkip = _len - _read; + if (leftToSkip > 0) + { + if (async) + await _buf.Skip(leftToSkip, async); + else + _buf.Skip(leftToSkip, async).GetAwaiter().GetResult(); + } + IsDisposed = true; + } + } + + static void ValidateArguments(byte[] buffer, int offset, int count) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + if (offset < 0) + throw new ArgumentNullException(nameof(offset)); + if (count < 0) + throw new ArgumentNullException(nameof(count)); + if (buffer.Length - offset < count) + throw new ArgumentException("Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection."); + } +} diff --git a/LibExternal/Npgsql/Internal/NpgsqlReadBuffer.cs b/LibExternal/Npgsql/Internal/NpgsqlReadBuffer.cs new file mode 100644 index 0000000..3b3d2f7 --- /dev/null +++ b/LibExternal/Npgsql/Internal/NpgsqlReadBuffer.cs @@ -0,0 +1,705 @@ +using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.Util; +using static System.Threading.Timeout; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Npgsql.Internal; + +/// +/// A buffer used by Npgsql to read data from the socket efficiently. +/// Provides methods which decode different values types and tracks the current position. +/// +public sealed partial class NpgsqlReadBuffer : IDisposable +{ + #region Fields and Properties + + public NpgsqlConnection Connection => Connector.Connection!; + + internal readonly NpgsqlConnector Connector; + + internal Stream Underlying { private get; set; } + + readonly Socket? _underlyingSocket; + + internal ResettableCancellationTokenSource Cts { get; } + + TimeSpan _preTranslatedTimeout = TimeSpan.Zero; + + /// + /// Timeout for sync and async reads + /// + internal TimeSpan Timeout + { + get => _preTranslatedTimeout; + set + { + if (_preTranslatedTimeout != value) + { + _preTranslatedTimeout = value; + + if (value == TimeSpan.Zero) + value = InfiniteTimeSpan; + else if (value < TimeSpan.Zero) + value = TimeSpan.Zero; + + Debug.Assert(_underlyingSocket != null); + + _underlyingSocket.ReceiveTimeout = (int)value.TotalMilliseconds; + Cts.Timeout = value; + } + } + } + + /// + /// The total byte length of the buffer. + /// + internal int Size { get; } + + internal Encoding TextEncoding { get; } + + /// + /// Same as , except that it does not throw an exception if an invalid char is + /// encountered (exception fallback), but rather replaces it with a question mark character (replacement + /// fallback). + /// + internal Encoding RelaxedTextEncoding { get; } + + internal int ReadPosition { get; set; } + internal int ReadBytesLeft => FilledBytes - ReadPosition; + + internal readonly byte[] Buffer; + internal int FilledBytes; + + ColumnStream? _columnStream; + + bool _usePool; + bool _disposed; + + /// + /// The minimum buffer size possible. + /// + internal const int MinimumSize = 4096; + internal const int DefaultSize = 8192; + + #endregion + + #region Constructors + + internal NpgsqlReadBuffer( + NpgsqlConnector connector, + Stream stream, + Socket? socket, + int size, + Encoding textEncoding, + Encoding relaxedTextEncoding, + bool usePool = false) + { + if (size < MinimumSize) + { + throw new ArgumentOutOfRangeException(nameof(size), size, "Buffer size must be at least " + MinimumSize); + } + + Connector = connector; + Underlying = stream; + _underlyingSocket = socket; + Cts = new ResettableCancellationTokenSource(); + Buffer = usePool ? ArrayPool.Shared.Rent(size) : new byte[size]; + Size = Buffer.Length; + _usePool = usePool; + + TextEncoding = textEncoding; + RelaxedTextEncoding = relaxedTextEncoding; + } + + #endregion + + #region I/O + + internal void Ensure(int count) + { + if (count <= ReadBytesLeft) + return; + Ensure(count, false).GetAwaiter().GetResult(); + } + + public Task Ensure(int count, bool async) + => Ensure(count, async, readingNotifications: false); + + public Task EnsureAsync(int count) + => Ensure(count, async: true, readingNotifications: false); + + /// + /// Ensures that bytes are available in the buffer, and if + /// not, reads from the socket until enough is available. + /// + internal Task Ensure(int count, bool async, bool readingNotifications) + { + return count <= ReadBytesLeft ? Task.CompletedTask : EnsureLong(this, count, async, readingNotifications); + + static async Task EnsureLong( + NpgsqlReadBuffer buffer, + int count, + bool async, + bool readingNotifications) + { + Debug.Assert(count <= buffer.Size); + Debug.Assert(count > buffer.ReadBytesLeft); + count -= buffer.ReadBytesLeft; + if (count <= 0) { return; } + + if (buffer.ReadPosition == buffer.FilledBytes) + { + buffer.Clear(); + } + else if (count > buffer.Size - buffer.FilledBytes) + { + Array.Copy(buffer.Buffer, buffer.ReadPosition, buffer.Buffer, 0, buffer.ReadBytesLeft); + buffer.FilledBytes = buffer.ReadBytesLeft; + buffer.ReadPosition = 0; + } + + var finalCt = async && buffer.Timeout != TimeSpan.Zero + ? buffer.Cts.Start() + : buffer.Cts.Reset(); + + var totalRead = 0; + while (count > 0) + { + try + { + var toRead = buffer.Size - buffer.FilledBytes; + var read = async + ? await buffer.Underlying.ReadAsync(buffer.Buffer.AsMemory(buffer.FilledBytes, toRead), finalCt) + : buffer.Underlying.Read(buffer.Buffer, buffer.FilledBytes, toRead); + + if (read == 0) + throw new EndOfStreamException(); + count -= read; + buffer.FilledBytes += read; + totalRead += read; + + // Most of the time, it should be fine to reset cancellation token source, so we can use it again + // It's still possible for cancellation token to cancel between reading and resetting (although highly improbable) + // In this case, we consider it as timed out and fail with OperationCancelledException on next ReadAsync + // Or we consider it not timed out if we have already read everything (count == 0) + // In which case we reinitialize it on the next call to EnsureLong() + if (async) + buffer.Cts.RestartTimeoutWithoutReset(); + } + catch (Exception e) + { + var connector = buffer.Connector; + + // Stopping twice (in case the previous Stop() call succeeded) doesn't hurt. + // Not stopping will cause an assertion failure in debug mode when we call Start() the next time. + // We can't stop in a finally block because Connector.Break() will dispose the buffer and the contained + // _timeoutCts + buffer.Cts.Stop(); + + switch (e) + { + // Read timeout + case OperationCanceledException: + // Note that mono throws SocketException with the wrong error (see #1330) + case IOException when (e.InnerException as SocketException)?.SocketErrorCode == + (Type.GetType("Mono.Runtime") == null ? SocketError.TimedOut : SocketError.WouldBlock): + { + Debug.Assert(e is OperationCanceledException ? async : !async); + + var isStreamBroken = false; +#if NETSTANDARD2_0 + // SslStream on .NET Framework treats any IOException (including timeouts) as fatal and may + // return garbage if reused. To prevent this, we flow down and break the connection immediately. + // See #4305. + isStreamBroken = connector.IsSecure && e is IOException; +#endif + + if (!isStreamBroken) + { + // When reading notifications (Wait), just throw TimeoutException or + // OperationCanceledException immediately. + // Nothing to cancel, and no breaking of the connection. + if (readingNotifications) + throw CreateException(connector); + + // If we should attempt PostgreSQL cancellation, do it the first time we get a timeout. + // TODO: As an optimization, we can still attempt to send a cancellation request, but after + // that immediately break the connection + if (connector.AttemptPostgresCancellation && + !connector.PostgresCancellationPerformed && + connector.PerformPostgresCancellation()) + { + // Note that if the cancellation timeout is negative, we flow down and break the + // connection immediately. + var cancellationTimeout = connector.Settings.CancellationTimeout; + if (cancellationTimeout >= 0) + { + if (cancellationTimeout > 0) + buffer.Timeout = TimeSpan.FromMilliseconds(cancellationTimeout); + + if (async) + finalCt = buffer.Cts.Start(); + + continue; + } + } + } + + // If we're here, the PostgreSQL cancellation either failed or skipped entirely. + // Break the connection, bubbling up the correct exception type (cancellation or timeout) + throw connector.Break(CreateException(connector)); + + static Exception CreateException(NpgsqlConnector connector) + => !connector.UserCancellationRequested + ? NpgsqlTimeoutException() + : connector.PostgresCancellationPerformed + ? new OperationCanceledException("Query was cancelled", TimeoutException(), connector.UserCancellationToken) + : new OperationCanceledException("Query was cancelled", connector.UserCancellationToken); + } + + default: + throw connector.Break(new NpgsqlException("Exception while reading from stream", e)); + } + } + } + + buffer.Cts.Stop(); + NpgsqlEventSource.Log.BytesRead(totalRead); + + static Exception NpgsqlTimeoutException() => new NpgsqlException("Exception while reading from stream", TimeoutException()); + + static Exception TimeoutException() => new TimeoutException("Timeout during reading attempt"); + } + } + + internal void ReadMore() => ReadMore(false).GetAwaiter().GetResult(); + + internal Task ReadMore(bool async) => Ensure(ReadBytesLeft + 1, async); + + internal NpgsqlReadBuffer AllocateOversize(int count) + { + Debug.Assert(count > Size); + var tempBuf = new NpgsqlReadBuffer(Connector, Underlying, _underlyingSocket, count, TextEncoding, RelaxedTextEncoding, usePool: true); + if (_underlyingSocket != null) + tempBuf.Timeout = Timeout; + CopyTo(tempBuf); + Clear(); + return tempBuf; + } + + /// + /// Does not perform any I/O - assuming that the bytes to be skipped are in the memory buffer. + /// + /// + internal void Skip(long len) + { + Debug.Assert(ReadBytesLeft >= len); + ReadPosition += (int)len; + } + + /// + /// Skip a given number of bytes. + /// + public async Task Skip(long len, bool async) + { + Debug.Assert(len >= 0); + + if (len > ReadBytesLeft) + { + len -= ReadBytesLeft; + while (len > Size) + { + Clear(); + await Ensure(Size, async); + len -= Size; + } + Clear(); + await Ensure((int)len, async); + } + + ReadPosition += (int)len; + } + + #endregion + + #region Read Simple + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public sbyte ReadSByte() => Read(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte ReadByte() => Read(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public short ReadInt16() + => ReadInt16(false); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public short ReadInt16(bool littleEndian) + { + var result = Read(); + return littleEndian == BitConverter.IsLittleEndian + ? result : BinaryPrimitives.ReverseEndianness(result); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort ReadUInt16() + => ReadUInt16(false); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort ReadUInt16(bool littleEndian) + { + var result = Read(); + return littleEndian == BitConverter.IsLittleEndian + ? result : BinaryPrimitives.ReverseEndianness(result); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int ReadInt32() + => ReadInt32(false); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int ReadInt32(bool littleEndian) + { + var result = Read(); + return littleEndian == BitConverter.IsLittleEndian + ? result : BinaryPrimitives.ReverseEndianness(result); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint ReadUInt32() + => ReadUInt32(false); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint ReadUInt32(bool littleEndian) + { + var result = Read(); + return littleEndian == BitConverter.IsLittleEndian + ? result : BinaryPrimitives.ReverseEndianness(result); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long ReadInt64() + => ReadInt64(false); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long ReadInt64(bool littleEndian) + { + var result = Read(); + return littleEndian == BitConverter.IsLittleEndian + ? result : BinaryPrimitives.ReverseEndianness(result); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong ReadUInt64() + => ReadUInt64(false); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong ReadUInt64(bool littleEndian) + { + var result = Read(); + return littleEndian == BitConverter.IsLittleEndian + ? result : BinaryPrimitives.ReverseEndianness(result); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float ReadSingle() + => ReadSingle(false); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float ReadSingle(bool littleEndian) + { + var result = ReadInt32(littleEndian); + return Unsafe.As(ref result); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public double ReadDouble() + => ReadDouble(false); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public double ReadDouble(bool littleEndian) + { + var result = ReadInt64(littleEndian); + return Unsafe.As(ref result); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + T Read() + { + if (Unsafe.SizeOf() > ReadBytesLeft) + ThrowNotSpaceLeft(); + + var result = Unsafe.ReadUnaligned(ref Buffer[ReadPosition]); + ReadPosition += Unsafe.SizeOf(); + return result; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void ThrowNotSpaceLeft() + => throw new InvalidOperationException("There is not enough space left in the buffer."); + + public string ReadString(int byteLen) + { + Debug.Assert(byteLen <= ReadBytesLeft); + var result = TextEncoding.GetString(Buffer, ReadPosition, byteLen); + ReadPosition += byteLen; + return result; + } + + public char[] ReadChars(int byteLen) + { + Debug.Assert(byteLen <= ReadBytesLeft); + var result = TextEncoding.GetChars(Buffer, ReadPosition, byteLen); + ReadPosition += byteLen; + return result; + } + + public void ReadBytes(Span output) + { + Debug.Assert(output.Length <= ReadBytesLeft); + new Span(Buffer, ReadPosition, output.Length).CopyTo(output); + ReadPosition += output.Length; + } + + public void ReadBytes(byte[] output, int outputOffset, int len) + => ReadBytes(new Span(output, outputOffset, len)); + + public ReadOnlySpan ReadSpan(int len) + { + Debug.Assert(len <= ReadBytesLeft); + var span = new ReadOnlySpan(Buffer, ReadPosition, len); + ReadPosition += len; + return span; + } + + public ReadOnlyMemory ReadMemory(int len) + { + Debug.Assert(len <= ReadBytesLeft); + var memory = new ReadOnlyMemory(Buffer, ReadPosition, len); + ReadPosition += len; + return memory; + } + + #endregion + + #region Read Complex + + public int Read(Span output) + { + var readFromBuffer = Math.Min(ReadBytesLeft, output.Length); + if (readFromBuffer > 0) + { + new Span(Buffer, ReadPosition, readFromBuffer).CopyTo(output); + ReadPosition += readFromBuffer; + return readFromBuffer; + } + + if (output.Length == 0) + return 0; + + Debug.Assert(ReadBytesLeft == 0); + Clear(); + try + { + var read = Underlying.Read(output); + if (read == 0) + throw new EndOfStreamException(); + return read; + } + catch (Exception e) + { + throw Connector.Break(new NpgsqlException("Exception while reading from stream", e)); + } + } + + public ValueTask ReadAsync(Memory output, CancellationToken cancellationToken = default) + { + if (output.Length == 0) + return new ValueTask(0); + + var readFromBuffer = Math.Min(ReadBytesLeft, output.Length); + if (readFromBuffer > 0) + { + new Span(Buffer, ReadPosition, readFromBuffer).CopyTo(output.Span); + ReadPosition += readFromBuffer; + return new ValueTask(readFromBuffer); + } + + return ReadAsyncLong(this, output, cancellationToken); + + static async ValueTask ReadAsyncLong(NpgsqlReadBuffer buffer, Memory output, CancellationToken cancellationToken) + { + Debug.Assert(buffer.ReadBytesLeft == 0); + buffer.Clear(); + try + { + var read = await buffer.Underlying.ReadAsync(output, cancellationToken); + if (read == 0) + throw new EndOfStreamException(); + return read; + } + catch (Exception e) + { + throw buffer.Connector.Break(new NpgsqlException("Exception while reading from stream", e)); + } + } + } + + public Stream GetStream(int len, bool canSeek) + { + if (_columnStream == null) + _columnStream = new ColumnStream(Connector); + + _columnStream.Init(len, canSeek); + return _columnStream; + } + + /// + /// Seeks the first null terminator (\0) and returns the string up to it. The buffer must already + /// contain the entire string and its terminator. + /// + public string ReadNullTerminatedString() + => ReadNullTerminatedString(TextEncoding, async: false).GetAwaiter().GetResult(); + + /// + /// Seeks the first null terminator (\0) and returns the string up to it. The buffer must already + /// contain the entire string and its terminator. If any character could not be decoded, a question + /// mark character is returned instead of throwing an exception. + /// + public string ReadNullTerminatedStringRelaxed() + => ReadNullTerminatedString(RelaxedTextEncoding, async: false).GetAwaiter().GetResult(); + + public ValueTask ReadNullTerminatedString(bool async, CancellationToken cancellationToken = default) + => ReadNullTerminatedString(TextEncoding, async, cancellationToken); + + /// + /// Seeks the first null terminator (\0) and returns the string up to it. Reads additional data from the network if a null + /// terminator isn't found in the buffered data. + /// + ValueTask ReadNullTerminatedString(Encoding encoding, bool async, CancellationToken cancellationToken = default) + { + for (var i = ReadPosition; i < FilledBytes; i++) + { + if (Buffer[i] == 0) + { + var byteLen = i - ReadPosition; + var result = new ValueTask(encoding.GetString(Buffer, ReadPosition, byteLen)); + ReadPosition += byteLen + 1; + return result; + } + } + + return ReadLong(async); + + async ValueTask ReadLong(bool async) + { + var chunkSize = FilledBytes - ReadPosition; + var tempBuf = ArrayPool.Shared.Rent(chunkSize + 1024); + + try + { + bool foundTerminator; + var byteLen = chunkSize; + Array.Copy(Buffer, ReadPosition, tempBuf, 0, chunkSize); + ReadPosition += chunkSize; + + do + { + await ReadMore(async); + Debug.Assert(ReadPosition == 0); + + foundTerminator = false; + int i; + for (i = 0; i < FilledBytes; i++) + { + if (Buffer[i] == 0) + { + foundTerminator = true; + break; + } + } + + if (byteLen + i > tempBuf.Length) + { + var newTempBuf = ArrayPool.Shared.Rent( + foundTerminator ? byteLen + i : byteLen + i + 1024); + + Array.Copy(tempBuf, 0, newTempBuf, 0, byteLen); + ArrayPool.Shared.Return(tempBuf); + tempBuf = newTempBuf; + } + + Array.Copy(Buffer, 0, tempBuf, byteLen, i); + byteLen += i; + ReadPosition = i; + } while (!foundTerminator); + + ReadPosition++; + return encoding.GetString(tempBuf, 0, byteLen); + } + finally + { + ArrayPool.Shared.Return(tempBuf); + } + } + } + + public ReadOnlySpan GetNullTerminatedBytes() + { + int i; + for (i = ReadPosition; Buffer[i] != 0; i++) + Debug.Assert(i <= ReadPosition + ReadBytesLeft); + Debug.Assert(i >= ReadPosition); + + var result = new ReadOnlySpan(Buffer, ReadPosition, i - ReadPosition); + ReadPosition = i + 1; + return result; + } + + #endregion + + #region Dispose + + public void Dispose() + { + if (_disposed) + return; + + if (_usePool) + ArrayPool.Shared.Return(Buffer); + + Cts.Dispose(); + _disposed = true; + } + + #endregion + + #region Misc + + internal void Clear() + { + ReadPosition = 0; + FilledBytes = 0; + } + + internal void CopyTo(NpgsqlReadBuffer other) + { + Debug.Assert(other.Size - other.FilledBytes >= ReadBytesLeft); + Array.Copy(Buffer, ReadPosition, other.Buffer, other.FilledBytes, ReadBytesLeft); + other.FilledBytes += ReadBytesLeft; + } + + #endregion +} diff --git a/LibExternal/Npgsql/Internal/NpgsqlWriteBuffer.Stream.cs b/LibExternal/Npgsql/Internal/NpgsqlWriteBuffer.Stream.cs new file mode 100644 index 0000000..46b5c8e --- /dev/null +++ b/LibExternal/Npgsql/Internal/NpgsqlWriteBuffer.Stream.cs @@ -0,0 +1,122 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Npgsql.Internal; + +public sealed partial class NpgsqlWriteBuffer +{ + sealed class ParameterStream : Stream + { + readonly NpgsqlWriteBuffer _buf; + bool _disposed; + + internal ParameterStream(NpgsqlWriteBuffer buf) + => _buf = buf; + + internal void Init() + => _disposed = false; + + public override bool CanRead => false; + + public override bool CanWrite => true; + + public override bool CanSeek => false; + + public override long Length => throw new NotSupportedException(); + + public override void SetLength(long value) + => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + => throw new NotSupportedException(); + + public override void Flush() + => CheckDisposed(); + + public override Task FlushAsync(CancellationToken cancellationToken = default) + { + CheckDisposed(); + return cancellationToken.IsCancellationRequested + ? Task.FromCanceled(cancellationToken) : Task.CompletedTask; + } + + public override int Read(byte[] buffer, int offset, int count) + => throw new NotSupportedException(); + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default) + => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) + => Write(buffer, offset, count, false); + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default) + { + using (NoSynchronizationContextScope.Enter()) + return Write(buffer, offset, count, true, cancellationToken); + } + + Task Write(byte[] buffer, int offset, int count, bool async, CancellationToken cancellationToken = default) + { + CheckDisposed(); + + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + if (offset < 0) + throw new ArgumentNullException(nameof(offset)); + if (count < 0) + throw new ArgumentNullException(nameof(count)); + if (buffer.Length - offset < count) + throw new ArgumentException("Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection."); + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + while (count > 0) + { + var left = _buf.WriteSpaceLeft; + if (left == 0) + return WriteLong(buffer, offset, count, async, cancellationToken); + + var slice = Math.Min(count, left); + _buf.WriteBytes(buffer, offset, slice); + offset += slice; + count -= slice; + } + + return Task.CompletedTask; + } + + async Task WriteLong(byte[] buffer, int offset, int count, bool async, CancellationToken cancellationToken = default) + { + while (count > 0) + { + var left = _buf.WriteSpaceLeft; + if (left == 0) + { + await _buf.Flush(async, cancellationToken); + continue; + } + var slice = Math.Min(count, left); + _buf.WriteBytes(buffer, offset, slice); + offset += slice; + count -= slice; + } + } + + void CheckDisposed() + { + if (_disposed) + throw new ObjectDisposedException(null); + } + + protected override void Dispose(bool disposing) + => _disposed = true; + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/NpgsqlWriteBuffer.cs b/LibExternal/Npgsql/Internal/NpgsqlWriteBuffer.cs new file mode 100644 index 0000000..e803825 --- /dev/null +++ b/LibExternal/Npgsql/Internal/NpgsqlWriteBuffer.cs @@ -0,0 +1,593 @@ +using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.Util; +using static System.Threading.Timeout; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +namespace Npgsql.Internal; + +/// +/// A buffer used by Npgsql to write data to the socket efficiently. +/// Provides methods which encode different values types and tracks the current position. +/// +public sealed partial class NpgsqlWriteBuffer : IDisposable +{ + #region Fields and Properties + + internal readonly NpgsqlConnector Connector; + + internal Stream Underlying { private get; set; } + + readonly Socket? _underlyingSocket; + + readonly ResettableCancellationTokenSource _timeoutCts; + + /// + /// Timeout for sync and async writes + /// + internal TimeSpan Timeout + { + get => _timeoutCts.Timeout; + set + { + if (_timeoutCts.Timeout != value) + { + Debug.Assert(_underlyingSocket != null); + + if (value > TimeSpan.Zero) + { + _underlyingSocket.SendTimeout = (int)value.TotalMilliseconds; + _timeoutCts.Timeout = value; + } + else + { + _underlyingSocket.SendTimeout = -1; + _timeoutCts.Timeout = InfiniteTimeSpan; + } + } + } + } + + /// + /// The total byte length of the buffer. + /// + internal int Size { get; private set; } + + bool _copyMode; + internal Encoding TextEncoding { get; } + + public int WriteSpaceLeft => Size - WritePosition; + + internal readonly byte[] Buffer; + readonly Encoder _textEncoder; + + internal int WritePosition; + + ParameterStream? _parameterStream; + + bool _disposed; + + /// + /// The minimum buffer size possible. + /// + internal const int MinimumSize = 4096; + internal const int DefaultSize = 8192; + + #endregion + + #region Constructors + + internal NpgsqlWriteBuffer( + NpgsqlConnector connector, + Stream stream, + Socket? socket, + int size, + Encoding textEncoding) + { + if (size < MinimumSize) + throw new ArgumentOutOfRangeException(nameof(size), size, "Buffer size must be at least " + MinimumSize); + + Connector = connector; + Underlying = stream; + _underlyingSocket = socket; + _timeoutCts = new ResettableCancellationTokenSource(); + Buffer = new byte[size]; + Size = size; + + TextEncoding = textEncoding; + _textEncoder = TextEncoding.GetEncoder(); + } + + #endregion + + #region I/O + + public async Task Flush(bool async, CancellationToken cancellationToken = default) + { + if (_copyMode) + { + // In copy mode, we write CopyData messages. The message code has already been + // written to the beginning of the buffer, but we need to go back and write the + // length. + if (WritePosition == 1) + return; + var pos = WritePosition; + WritePosition = 1; + WriteInt32(pos - 1); + WritePosition = pos; + } else if (WritePosition == 0) + return; + + var finalCt = cancellationToken; + if (async && Timeout > TimeSpan.Zero) + finalCt = _timeoutCts.Start(cancellationToken); + + try + { + if (async) + { + await Underlying.WriteAsync(Buffer, 0, WritePosition, finalCt); + await Underlying.FlushAsync(finalCt); + _timeoutCts.Stop(); + } + else + { + Underlying.Write(Buffer, 0, WritePosition); + Underlying.Flush(); + } + } + catch (Exception e) + { + // Stopping twice (in case the previous Stop() call succeeded) doesn't hurt. + // Not stopping will cause an assertion failure in debug mode when we call Start() the next time. + // We can't stop in a finally block because Connector.Break() will dispose the buffer and the contained + // _timeoutCts + _timeoutCts.Stop(); + switch (e) + { + // User requested the cancellation + case OperationCanceledException _ when (cancellationToken.IsCancellationRequested): + throw Connector.Break(e); + // Read timeout + case OperationCanceledException _: + // Note that mono throws SocketException with the wrong error (see #1330) + case IOException _ when (e.InnerException as SocketException)?.SocketErrorCode == + (Type.GetType("Mono.Runtime") == null ? SocketError.TimedOut : SocketError.WouldBlock): + Debug.Assert(e is OperationCanceledException ? async : !async); + throw Connector.Break(new NpgsqlException("Exception while writing to stream", new TimeoutException("Timeout during writing attempt"))); + } + + throw Connector.Break(new NpgsqlException("Exception while writing to stream", e)); + } + NpgsqlEventSource.Log.BytesWritten(WritePosition); + //NpgsqlEventSource.Log.RequestFailed(); + + WritePosition = 0; + if (_copyMode) + WriteCopyDataHeader(); + } + + internal void Flush() => Flush(false).GetAwaiter().GetResult(); + + #endregion + + #region Direct write + + internal void DirectWrite(ReadOnlySpan buffer) + { + Flush(); + + if (_copyMode) + { + // Flush has already written the CopyData header for us, but write the CopyData + // header to the socket with the write length before we can start writing the data directly. + Debug.Assert(WritePosition == 5); + + WritePosition = 1; + WriteInt32(buffer.Length + 4); + WritePosition = 5; + _copyMode = false; + Flush(); + _copyMode = true; + WriteCopyDataHeader(); // And ready the buffer after the direct write completes + } + else + Debug.Assert(WritePosition == 0); + + try + { + Underlying.Write(buffer); + } + catch (Exception e) + { + throw Connector.Break(new NpgsqlException("Exception while writing to stream", e)); + } + } + + internal async Task DirectWrite(ReadOnlyMemory memory, bool async, CancellationToken cancellationToken = default) + { + await Flush(async, cancellationToken); + + if (_copyMode) + { + // Flush has already written the CopyData header for us, but write the CopyData + // header to the socket with the write length before we can start writing the data directly. + Debug.Assert(WritePosition == 5); + + WritePosition = 1; + WriteInt32(memory.Length + 4); + WritePosition = 5; + _copyMode = false; + await Flush(async, cancellationToken); + _copyMode = true; + WriteCopyDataHeader(); // And ready the buffer after the direct write completes + } + else + Debug.Assert(WritePosition == 0); + + try + { + if (async) + await Underlying.WriteAsync(memory, cancellationToken); + else + Underlying.Write(memory.Span); + } + catch (Exception e) + { + throw Connector.Break(new NpgsqlException("Exception while writing to stream", e)); + } + } + + #endregion Direct write + + #region Write Simple + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteSByte(sbyte value) => Write(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteByte(byte value) => Write(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void WriteInt16(int value) + => WriteInt16((short)value, false); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt16(short value) + => WriteInt16(value, false); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt16(short value, bool littleEndian) + => Write(littleEndian == BitConverter.IsLittleEndian ? value : BinaryPrimitives.ReverseEndianness(value)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt16(ushort value) + => WriteUInt16(value, false); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt16(ushort value, bool littleEndian) + => Write(littleEndian == BitConverter.IsLittleEndian ? value : BinaryPrimitives.ReverseEndianness(value)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt32(int value) + => WriteInt32(value, false); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt32(int value, bool littleEndian) + => Write(littleEndian == BitConverter.IsLittleEndian ? value : BinaryPrimitives.ReverseEndianness(value)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt32(uint value) + => WriteUInt32(value, false); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt32(uint value, bool littleEndian) + => Write(littleEndian == BitConverter.IsLittleEndian ? value : BinaryPrimitives.ReverseEndianness(value)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt64(long value) + => WriteInt64(value, false); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt64(long value, bool littleEndian) + => Write(littleEndian == BitConverter.IsLittleEndian ? value : BinaryPrimitives.ReverseEndianness(value)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt64(ulong value) + => WriteUInt64(value, false); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt64(ulong value, bool littleEndian) + => Write(littleEndian == BitConverter.IsLittleEndian ? value : BinaryPrimitives.ReverseEndianness(value)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteSingle(float value) + => WriteSingle(value, false); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteSingle(float value, bool littleEndian) + => WriteInt32(Unsafe.As(ref value), littleEndian); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteDouble(double value) + => WriteDouble(value, false); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteDouble(double value, bool littleEndian) + => WriteInt64(Unsafe.As(ref value), littleEndian); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void Write(T value) + { + if (Unsafe.SizeOf() > WriteSpaceLeft) + ThrowNotSpaceLeft(); + + Unsafe.WriteUnaligned(ref Buffer[WritePosition], value); + WritePosition += Unsafe.SizeOf(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void ThrowNotSpaceLeft() + => throw new InvalidOperationException("There is not enough space left in the buffer."); + + public Task WriteString(string s, int byteLen, bool async, CancellationToken cancellationToken = default) + => WriteString(s, s.Length, byteLen, async, cancellationToken); + + public Task WriteString(string s, int charLen, int byteLen, bool async, CancellationToken cancellationToken = default) + { + if (byteLen <= WriteSpaceLeft) + { + WriteString(s, charLen); + return Task.CompletedTask; + } + return WriteStringLong(this, async, s, charLen, byteLen, cancellationToken); + + static async Task WriteStringLong(NpgsqlWriteBuffer buffer, bool async, string s, int charLen, int byteLen, CancellationToken cancellationToken) + { + Debug.Assert(byteLen > buffer.WriteSpaceLeft); + if (byteLen <= buffer.Size) + { + // String can fit entirely in an empty buffer. Flush and retry rather than + // going into the partial writing flow below (which requires ToCharArray()) + await buffer.Flush(async, cancellationToken); + buffer.WriteString(s, charLen); + } + else + { + var charPos = 0; + while (true) + { + buffer.WriteStringChunked(s, charPos, charLen - charPos, true, out var charsUsed, out var completed); + if (completed) + break; + await buffer.Flush(async, cancellationToken); + charPos += charsUsed; + } + } + } + } + + internal Task WriteChars(char[] chars, int offset, int charLen, int byteLen, bool async, CancellationToken cancellationToken = default) + { + if (byteLen <= WriteSpaceLeft) + { + WriteChars(chars, offset, charLen); + return Task.CompletedTask; + } + return WriteCharsLong(this, async, chars, offset, charLen, byteLen, cancellationToken); + + static async Task WriteCharsLong(NpgsqlWriteBuffer buffer, bool async, char[] chars, int offset, int charLen, int byteLen, CancellationToken cancellationToken) + { + Debug.Assert(byteLen > buffer.WriteSpaceLeft); + if (byteLen <= buffer.Size) + { + // String can fit entirely in an empty buffer. Flush and retry rather than + // going into the partial writing flow below (which requires ToCharArray()) + await buffer.Flush(async, cancellationToken); + buffer.WriteChars(chars, offset, charLen); + } + else + { + var charPos = 0; + + while (true) + { + buffer.WriteStringChunked(chars, charPos + offset, charLen - charPos, true, out var charsUsed, out var completed); + if (completed) + break; + await buffer.Flush(async, cancellationToken); + charPos += charsUsed; + } + } + } + } + + public void WriteString(string s, int len = 0) + { + Debug.Assert(TextEncoding.GetByteCount(s) <= WriteSpaceLeft); + WritePosition += TextEncoding.GetBytes(s, 0, len == 0 ? s.Length : len, Buffer, WritePosition); + } + + internal void WriteChars(char[] chars, int offset, int len) + { + var charCount = len == 0 ? chars.Length : len; + Debug.Assert(TextEncoding.GetByteCount(chars, 0, charCount) <= WriteSpaceLeft); + WritePosition += TextEncoding.GetBytes(chars, offset, charCount, Buffer, WritePosition); + } + + public void WriteBytes(ReadOnlySpan buf) + { + Debug.Assert(buf.Length <= WriteSpaceLeft); + buf.CopyTo(new Span(Buffer, WritePosition, Buffer.Length - WritePosition)); + WritePosition += buf.Length; + } + + public void WriteBytes(byte[] buf, int offset, int count) + => WriteBytes(new ReadOnlySpan(buf, offset, count)); + + public Task WriteBytesRaw(byte[] bytes, bool async, CancellationToken cancellationToken = default) + { + if (bytes.Length <= WriteSpaceLeft) + { + WriteBytes(bytes); + return Task.CompletedTask; + } + return WriteBytesLong(this, async, bytes, cancellationToken); + + static async Task WriteBytesLong(NpgsqlWriteBuffer buffer, bool async, byte[] bytes, CancellationToken cancellationToken) + { + if (bytes.Length <= buffer.Size) + { + // value can fit entirely in an empty buffer. Flush and retry rather than + // going into the partial writing flow below + await buffer.Flush(async, cancellationToken); + buffer.WriteBytes(bytes); + } + else + { + var remaining = bytes.Length; + do + { + if (buffer.WriteSpaceLeft == 0) + await buffer.Flush(async, cancellationToken); + var writeLen = Math.Min(remaining, buffer.WriteSpaceLeft); + var offset = bytes.Length - remaining; + buffer.WriteBytes(bytes, offset, writeLen); + remaining -= writeLen; + } + while (remaining > 0); + } + } + } + + public void WriteNullTerminatedString(string s) + { + Debug.Assert(s.All(c => c < 128), "Method only supports ASCII strings"); + Debug.Assert(WriteSpaceLeft >= s.Length + 1); + WritePosition += Encoding.ASCII.GetBytes(s, 0, s.Length, Buffer, WritePosition); + WriteByte(0); + } + + #endregion + + #region Write Complex + + public Stream GetStream() + { + if (_parameterStream == null) + _parameterStream = new ParameterStream(this); + + _parameterStream.Init(); + return _parameterStream; + } + + internal void WriteStringChunked(char[] chars, int charIndex, int charCount, + bool flush, out int charsUsed, out bool completed) + { + if (WriteSpaceLeft < _textEncoder.GetByteCount(chars, charIndex, char.IsHighSurrogate(chars[charIndex]) ? 2 : 1, flush: false)) + { + charsUsed = 0; + completed = false; + return; + } + + _textEncoder.Convert(chars, charIndex, charCount, Buffer, WritePosition, WriteSpaceLeft, + flush, out charsUsed, out var bytesUsed, out completed); + WritePosition += bytesUsed; + } + + internal unsafe void WriteStringChunked(string s, int charIndex, int charCount, + bool flush, out int charsUsed, out bool completed) + { + int bytesUsed; + + fixed (char* sPtr = s) + fixed (byte* bufPtr = Buffer) + { + if (WriteSpaceLeft < _textEncoder.GetByteCount(sPtr + charIndex, char.IsHighSurrogate(*(sPtr + charIndex)) ? 2 : 1, flush: false)) + { + charsUsed = 0; + completed = false; + return; + } + + _textEncoder.Convert(sPtr + charIndex, charCount, bufPtr + WritePosition, WriteSpaceLeft, + flush, out charsUsed, out bytesUsed, out completed); + } + + WritePosition += bytesUsed; + } + + #endregion + + #region Copy + + internal void StartCopyMode() + { + _copyMode = true; + Size -= 5; + WriteCopyDataHeader(); + } + + internal void EndCopyMode() + { + // EndCopyMode is usually called after a Flush which ended the last CopyData message. + // That Flush also wrote the header for another CopyData which we clear here. + _copyMode = false; + Size += 5; + Clear(); + } + + void WriteCopyDataHeader() + { + Debug.Assert(_copyMode); + Debug.Assert(WritePosition == 0); + WriteByte(FrontendMessageCode.CopyData); + // Leave space for the message length + WriteInt32(0); + } + + #endregion + + #region Dispose + + public void Dispose() + { + if (_disposed) + return; + + _timeoutCts.Dispose(); + _disposed = true; + } + + #endregion + + #region Misc + + internal void Clear() + { + WritePosition = 0; + } + + /// + /// Returns all contents currently written to the buffer (but not flushed). + /// Useful for pre-generating messages. + /// + internal byte[] GetContents() + { + var buf = new byte[WritePosition]; + Array.Copy(Buffer, buf, WritePosition); + return buf; + } + + #endregion +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/ArrayHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/ArrayHandler.cs new file mode 100644 index 0000000..54c460d --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/ArrayHandler.cs @@ -0,0 +1,516 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers; + +/// +/// Non-generic base class for all type handlers which handle PostgreSQL arrays. +/// Extend from instead. +/// +/// +/// https://www.postgresql.org/docs/current/static/arrays.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public abstract class ArrayHandler : NpgsqlTypeHandler +{ + private protected int LowerBound { get; } // The lower bound value sent to the backend when writing arrays. Normally 1 (the PG default) but is 0 for OIDVector. + private protected NpgsqlTypeHandler ElementHandler { get; } + private protected ArrayNullabilityMode ArrayNullabilityMode { get; } + + static readonly MethodInfo ReadArrayMethod = typeof(ArrayHandler).GetMethod(nameof(ReadArray), BindingFlags.NonPublic | BindingFlags.Instance)!; + static readonly MethodInfo ReadListMethod = typeof(ArrayHandler).GetMethod(nameof(ReadList), BindingFlags.NonPublic | BindingFlags.Instance)!; + + /// + protected ArrayHandler(PostgresType arrayPostgresType, NpgsqlTypeHandler elementHandler, ArrayNullabilityMode arrayNullabilityMode, int lowerBound = 1) + : base(arrayPostgresType) + { + LowerBound = lowerBound; + ElementHandler = elementHandler; + ArrayNullabilityMode = arrayNullabilityMode; + } + + public override Type GetFieldType(FieldDescription? fieldDescription = null) => typeof(Array); + public override Type GetProviderSpecificFieldType(FieldDescription? fieldDescription = null) => typeof(Array); + + /// + public override NpgsqlTypeHandler CreateArrayHandler(PostgresArrayType pgArrayType, ArrayNullabilityMode arrayNullabilityMode) + => throw new NotSupportedException(); + + /// + public override NpgsqlTypeHandler CreateRangeHandler(PostgresType pgRangeType) + => throw new NotSupportedException(); + + /// + public override NpgsqlTypeHandler CreateMultirangeHandler(PostgresMultirangeType pgMultirangeType) + => throw new NotSupportedException(); + + #region Read + + /// + protected internal override async ValueTask ReadCustom(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + { + if (ArrayTypeInfo.IsArray) + return (TRequestedArray)(object)await ArrayTypeInfo.ReadArrayFunc(this, buf, async); + + if (ArrayTypeInfo.IsList) + return await ArrayTypeInfo.ReadListFunc(this, buf, async); + + throw new InvalidCastException(fieldDescription == null + ? $"Can't cast database type to {typeof(TRequestedArray).Name}" + : $"Can't cast database type {fieldDescription.Handler.PgDisplayName} to {typeof(TRequestedArray).Name}" + ); + } + + /// + /// Reads an array of element type from the given buffer . + /// + protected async ValueTask ReadArray(NpgsqlReadBuffer buf, bool async, int expectedDimensions = 0, bool readAsObject = false) + { + await buf.Ensure(12, async); + var dimensions = buf.ReadInt32(); + var containsNulls = buf.ReadInt32() == 1; + buf.ReadUInt32(); // Element OID. Ignored. + + var returnType = readAsObject + ? ArrayNullabilityMode switch + { + ArrayNullabilityMode.Never => ElementTypeInfo.IsNonNullable && containsNulls + ? throw new InvalidOperationException(ReadNonNullableCollectionWithNullsExceptionMessage) + : typeof(TRequestedElement), + ArrayNullabilityMode.Always => ElementTypeInfo.NullableElementType, + ArrayNullabilityMode.PerInstance => containsNulls + ? ElementTypeInfo.NullableElementType + : typeof(TRequestedElement), + _ => throw new ArgumentOutOfRangeException() + } + : ElementTypeInfo.IsNonNullable && containsNulls + ? throw new InvalidOperationException(ReadNonNullableCollectionWithNullsExceptionMessage) + : typeof(TRequestedElement); + + if (dimensions == 0) + return expectedDimensions > 1 + ? Array.CreateInstance(returnType, new int[expectedDimensions]) + : returnType == typeof(TRequestedElement) + ? Array.Empty() + : Array.CreateInstance(returnType, 0); + + if (expectedDimensions > 0 && dimensions != expectedDimensions) + throw new InvalidOperationException($"Cannot read an array with {expectedDimensions} dimension(s) from an array with {dimensions} dimension(s)"); + + if (dimensions == 1 && returnType == typeof(TRequestedElement)) + { + await buf.Ensure(8, async); + var arrayLength = buf.ReadInt32(); + + buf.ReadInt32(); // Lower bound + + var oneDimensional = new TRequestedElement[arrayLength]; + for (var i = 0; i < oneDimensional.Length; i++) + oneDimensional[i] = await ElementHandler.ReadWithLength(buf, async); + + return oneDimensional; + } + + var dimLengths = new int[dimensions]; + await buf.Ensure(dimensions * 8, async); + + for (var i = 0; i < dimensions; i++) + { + dimLengths[i] = buf.ReadInt32(); + buf.ReadInt32(); // Lower bound + } + + var result = Array.CreateInstance(returnType, dimLengths); + + // Either multidimensional arrays or arrays of nullable value types requested as object + // We can't avoid boxing here + var indices = new int[dimensions]; + while (true) + { + await buf.Ensure(4, async); + var len = buf.ReadInt32(); + var element = len == -1 + ? (object?)null + : NullableHandler.Exists + ? await NullableHandler.ReadAsync!(ElementHandler, buf, len, async) + : await ElementHandler.Read(buf, len, async); + + result.SetValue(element, indices); + + // TODO: Overly complicated/inefficient... + indices[dimensions - 1]++; + for (var dim = dimensions - 1; dim >= 0; dim--) + { + if (indices[dim] <= result.GetUpperBound(dim)) + continue; + + if (dim == 0) + return result; + + for (var j = dim; j < dimensions; j++) + indices[j] = result.GetLowerBound(j); + indices[dim - 1]++; + } + } + } + + /// + /// Reads a generic list containing elements of type from the given buffer . + /// + protected async ValueTask> ReadList(NpgsqlReadBuffer buf, bool async) + { + await buf.Ensure(12, async); + var dimensions = buf.ReadInt32(); + var containsNulls = buf.ReadInt32() == 1; + buf.ReadUInt32(); // Element OID. Ignored. + + if (dimensions == 0) + return new List(); + if (dimensions > 1) + throw new NotSupportedException($"Can't read multidimensional array as List<{typeof(TRequestedElement).Name}>"); + if (ElementTypeInfo.IsNonNullable && containsNulls) + throw new InvalidOperationException(ReadNonNullableCollectionWithNullsExceptionMessage); + + await buf.Ensure(8, async); + var length = buf.ReadInt32(); + buf.ReadInt32(); // We don't care about the lower bounds + + var list = new List(length); + for (var i = 0; i < length; i++) + list.Add(await ElementHandler.ReadWithLength(buf, async)); + return list; + } + + internal const string ReadNonNullableCollectionWithNullsExceptionMessage = + "Cannot read a non-nullable collection of elements because the returned array contains nulls. " + + "Call GetFieldValue with a nullable array instead."; + + #endregion Read + + #region Static generic caching helpers + + internal static class ElementTypeInfo + { + public static readonly bool IsNonNullable = + typeof(TElement).IsValueType && Nullable.GetUnderlyingType(typeof(TElement)) is null; + + public static readonly Type NullableElementType = IsNonNullable + ? typeof(Nullable<>).MakeGenericType(typeof(TElement)) + : typeof(TElement); + } + + internal static class ArrayTypeInfo + { + // ReSharper disable StaticMemberInGenericType + public static readonly bool IsArray; + public static readonly bool IsList; + public static readonly Type? ElementType; + + public static readonly Func> ReadArrayFunc = default!; + public static readonly Func> ReadListFunc = default!; + // ReSharper restore StaticMemberInGenericType + + static ArrayTypeInfo() + { + var type = typeof(TArrayOrList); + IsArray = type.IsArray; + IsList = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>); + + ElementType = IsArray + ? type.GetElementType() + : IsList + ? type.GetGenericArguments()[0] + : null; + + if (ElementType == null) + return; + + // Initialize delegates + var arrayHandlerParam = Expression.Parameter(typeof(ArrayHandler), "arrayHandler"); + var bufferParam = Expression.Parameter(typeof(NpgsqlReadBuffer), "buf"); + var asyncParam = Expression.Parameter(typeof(bool), "async"); + + if (IsArray) + { + ReadArrayFunc = Expression + .Lambda>>( + Expression.Call( + arrayHandlerParam, + ReadArrayMethod.MakeGenericMethod(ElementType), + bufferParam, asyncParam, Expression.Constant(type.GetArrayRank()), Expression.Constant(false, typeof(bool))), + arrayHandlerParam, bufferParam, asyncParam) + .Compile(); + } + + if (IsList) + { + ReadListFunc = Expression + .Lambda>>( + Expression.Call( + arrayHandlerParam, + ReadListMethod.MakeGenericMethod(ElementType), + bufferParam, asyncParam), + arrayHandlerParam, bufferParam, asyncParam) + .Compile(); + } + } + } + + #endregion Static generic caching helpers +} + +/// +/// Base class for all type handlers which handle PostgreSQL arrays. +/// +/// +/// https://www.postgresql.org/docs/current/static/arrays.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public class ArrayHandler : ArrayHandler +{ + /// + public ArrayHandler(PostgresType arrayPostgresType, NpgsqlTypeHandler elementHandler, ArrayNullabilityMode arrayNullabilityMode, int lowerBound = 1) + : base(arrayPostgresType, elementHandler, arrayNullabilityMode, lowerBound) {} + + #region Read + + public override async ValueTask ReadAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + => await ReadArray(buf, async, readAsObject: true); + + #endregion + + #region Write + + static Exception MixedTypesOrJaggedArrayException(Exception innerException) + => new("While trying to write an array, one of its elements failed validation. " + + "You may be trying to mix types in a non-generic IList, or to write a jagged array.", innerException); + + static Exception CantWriteTypeException(Type type) + => new InvalidCastException($"Can't write type {type} as an array of {typeof(TElement)}"); + + // Since TAny isn't constrained to class? or struct (C# doesn't have a non-nullable constraint that doesn't limit us to either struct or class), + // we must use the bang operator here to tell the compiler that a null value will never be returned. + + /// + protected internal override int ValidateAndGetLengthCustom([DisallowNull] TAny value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLength(value, ref lengthCache); + + /// + public override int ValidateObjectAndGetLength(object? value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value is null || value == DBNull.Value + ? 0 + : ValidateAndGetLength(value!, ref lengthCache); + + int ValidateAndGetLength(object value, ref NpgsqlLengthCache? lengthCache) + { + lengthCache ??= new NpgsqlLengthCache(1); + if (lengthCache.IsPopulated) + return lengthCache.Get(); + if (value is ICollection generic) + return ValidateAndGetLengthGeneric(generic, ref lengthCache); + if (value is ICollection nonGeneric) + return ValidateAndGetLengthNonGeneric(nonGeneric, ref lengthCache); + throw CantWriteTypeException(value.GetType()); + } + + // Handle single-dimensional arrays and generic IList + int ValidateAndGetLengthGeneric(ICollection value, ref NpgsqlLengthCache lengthCache) + { + // Leave empty slot for the entire array length, and go ahead an populate the element slots + var pos = lengthCache.Position; + var len = + 4 + // dimensions + 4 + // has_nulls (unused) + 4 + // type OID + 1 * 8 + // number of dimensions (1) * (length + lower bound) + 4 * value.Count; // sum of element lengths + + lengthCache.Set(0); + var elemLengthCache = lengthCache; + + foreach (var element in value) + { + if (element is null) + continue; + + try + { + len += ElementHandler.ValidateAndGetLength(element, ref elemLengthCache, null); + } + catch (Exception e) + { + throw MixedTypesOrJaggedArrayException(e); + } + } + + lengthCache.Lengths[pos] = len; + return len; + } + + // Take care of multi-dimensional arrays and non-generic IList, we have no choice but to box/unbox + int ValidateAndGetLengthNonGeneric(ICollection value, ref NpgsqlLengthCache lengthCache) + { + var asMultidimensional = value as Array; + var dimensions = asMultidimensional?.Rank ?? 1; + + // Leave empty slot for the entire array length, and go ahead an populate the element slots + var pos = lengthCache.Position; + var len = + 4 + // dimensions + 4 + // has_nulls (unused) + 4 + // type OID + dimensions * 8 + // number of dimensions * (length + lower bound) + 4 * value.Count; // sum of element lengths + + lengthCache.Set(0); + NpgsqlLengthCache? elemLengthCache = lengthCache; + + foreach (var element in value) + { + if (element is null) + continue; + + try + { + len += ElementHandler.ValidateObjectAndGetLength(element, ref elemLengthCache, null); + } + catch (Exception e) + { + throw MixedTypesOrJaggedArrayException(e); + } + } + + lengthCache.Lengths[pos] = len; + return len; + } + + protected override Task WriteWithLengthCustom([DisallowNull] TAny value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken) + { + buf.WriteInt32(ValidateAndGetLength(value, ref lengthCache, parameter)); + + if (value is ICollection list) + return WriteGeneric(list, buf, lengthCache, async, cancellationToken); + + if (value is ICollection nonGeneric) + return WriteNonGeneric(nonGeneric, buf, lengthCache, async, cancellationToken); + + throw CantWriteTypeException(value.GetType()); + } + + // The default WriteObjectWithLength casts the type handler to INpgsqlTypeHandler, but that's not sufficient for + // us (need to handle many types of T, e.g. int[], int[,]...) + /// + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value is null || value is DBNull + ? WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken) + : WriteWithLength(value, buf, lengthCache, parameter, async, cancellationToken); + + async Task WriteGeneric(ICollection value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, bool async, CancellationToken cancellationToken = default) + { + var len = + 4 + // dimensions + 4 + // has_nulls (unused) + 4 + // type OID + 1 * 8; // number of dimensions (1) * (length + lower bound) + if (buf.WriteSpaceLeft < len) + { + await buf.Flush(async, cancellationToken); + Debug.Assert(buf.WriteSpaceLeft >= len, "Buffer too small for header"); + } + + buf.WriteInt32(1); + buf.WriteInt32(1); // has_nulls = 1. Not actually used by the backend. + buf.WriteUInt32(ElementHandler.PostgresType.OID); + buf.WriteInt32(value.Count); + buf.WriteInt32(LowerBound); // We don't map .NET lower bounds to PG + + foreach (var element in value) + await ElementHandler.WriteWithLength(element, buf, lengthCache, null, async, cancellationToken); + } + + async Task WriteNonGeneric(ICollection value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, bool async, CancellationToken cancellationToken = default) + { + var asArray = value as Array; + var dimensions = asArray?.Rank ?? 1; + + var len = + 4 + // ndim + 4 + // has_nulls + 4 + // element_oid + dimensions * 8; // dim (4) + lBound (4) + + if (buf.WriteSpaceLeft < len) + { + await buf.Flush(async, cancellationToken); + Debug.Assert(buf.WriteSpaceLeft >= len, "Buffer too small for header"); + } + + buf.WriteInt32(dimensions); + buf.WriteInt32(1); // HasNulls=1. Not actually used by the backend. + buf.WriteUInt32(ElementHandler.PostgresType.OID); + if (asArray != null) + { + for (var i = 0; i < dimensions; i++) + { + buf.WriteInt32(asArray.GetLength(i)); + buf.WriteInt32(LowerBound); // We don't map .NET lower bounds to PG + } + } + else + { + buf.WriteInt32(value.Count); + buf.WriteInt32(LowerBound); // We don't map .NET lower bounds to PG + } + + foreach (var element in value) + await ElementHandler.WriteObjectWithLength(element, buf, lengthCache, null, async, cancellationToken); + } + + #endregion +} + +/// +/// https://www.postgresql.org/docs/current/static/arrays.html +/// +/// The .NET type contained as an element within this array +/// The .NET provider-specific type contained as an element within this array +class ArrayHandlerWithPsv : ArrayHandler +{ + public ArrayHandlerWithPsv(PostgresType arrayPostgresType, NpgsqlTypeHandler elementHandler, ArrayNullabilityMode arrayNullabilityMode) + : base(arrayPostgresType, elementHandler, arrayNullabilityMode) { } + + protected internal override async ValueTask ReadCustom(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + { + if (ArrayTypeInfo.ElementType == typeof(TElementPsv)) + { + if (ArrayTypeInfo.IsArray) + return (TRequestedArray)(object)await ReadArray(buf, async, typeof(TRequestedArray).GetArrayRank()); + + if (ArrayTypeInfo.IsList) + return (TRequestedArray)(object)await ReadList(buf, async); + } + return await base.ReadCustom(buf, len, async, fieldDescription); + } + + internal override object ReadPsvAsObject(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => ReadPsvAsObject(buf, len, false, fieldDescription).GetAwaiter().GetResult(); + + internal override async ValueTask ReadPsvAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + => await ReadArray(buf, async, readAsObject: true); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/BitStringHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/BitStringHandler.cs new file mode 100644 index 0000000..7cf4701 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/BitStringHandler.cs @@ -0,0 +1,330 @@ +using System.Collections; +using System.Collections.Specialized; +using System.Diagnostics; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers; + +/// +/// A type handler for the PostgreSQL bit string data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-bit.html. +/// +/// Note that for BIT(1), this handler will return a bool by default, to align with SQLClient +/// (see discussion https://github.com/npgsql/npgsql/pull/362#issuecomment-59622101). +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class BitStringHandler : NpgsqlTypeHandler, + INpgsqlTypeHandler, INpgsqlTypeHandler, INpgsqlTypeHandler +{ + public BitStringHandler(PostgresType pgType) : base(pgType) { } + + public override Type GetFieldType(FieldDescription? fieldDescription = null) + => fieldDescription != null && fieldDescription.TypeModifier == 1 ? typeof(bool) : typeof(BitArray); + + public override Type GetProviderSpecificFieldType(FieldDescription? fieldDescription = null) + => GetFieldType(fieldDescription); + + // BitString requires a special array handler which returns bool or BitArray + /// + public override NpgsqlTypeHandler CreateArrayHandler(PostgresArrayType pgArrayType, ArrayNullabilityMode arrayNullabilityMode) + => new BitStringArrayHandler(pgArrayType, this, arrayNullabilityMode); + + #region Read + + /// + public override async ValueTask Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + { + await buf.Ensure(4, async); + var numBits = buf.ReadInt32(); + var result = new BitArray(numBits); + var bytesLeft = len - 4; // Remove leading number of bits + if (bytesLeft == 0) + return result; + + var bitNo = 0; + while (true) + { + var iterationEndPos = bytesLeft > buf.ReadBytesLeft + ? bytesLeft - buf.ReadBytesLeft + : 1; + + for (; bytesLeft > iterationEndPos; bytesLeft--) + { + // ReSharper disable ShiftExpressionRealShiftCountIsZero + var chunk = buf.ReadByte(); + result[bitNo++] = (chunk & (1 << 7)) != 0; + result[bitNo++] = (chunk & (1 << 6)) != 0; + result[bitNo++] = (chunk & (1 << 5)) != 0; + result[bitNo++] = (chunk & (1 << 4)) != 0; + result[bitNo++] = (chunk & (1 << 3)) != 0; + result[bitNo++] = (chunk & (1 << 2)) != 0; + result[bitNo++] = (chunk & (1 << 1)) != 0; + result[bitNo++] = (chunk & (1 << 0)) != 0; + } + + if (bytesLeft == 1) + break; + + Debug.Assert(buf.ReadBytesLeft == 0); + await buf.Ensure(Math.Min(bytesLeft, buf.Size), async); + } + + if (bitNo < result.Length) + { + var remainder = result.Length - bitNo; + await buf.Ensure(1, async); + var lastChunk = buf.ReadByte(); + for (var i = 7; i >= 8 - remainder; i--) + result[bitNo++] = (lastChunk & (1 << i)) != 0; + } + + return result; + } + + async ValueTask INpgsqlTypeHandler.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + { + if (len > 4 + 4) + throw new InvalidCastException("Can't read PostgreSQL bitstring with more than 32 bits into BitVector32"); + + await buf.Ensure(4 + 4, async); + + var numBits = buf.ReadInt32(); + return numBits == 0 + ? new BitVector32(0) + : new BitVector32(buf.ReadInt32()); + } + + async ValueTask INpgsqlTypeHandler.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + { + await buf.Ensure(5, async); + var bitLen = buf.ReadInt32(); + if (bitLen != 1) + throw new InvalidCastException("Can't convert a BIT(N) type to bool, only BIT(1)"); + var b = buf.ReadByte(); + return (b & 128) != 0; + } + + ValueTask INpgsqlTypeHandler.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + => throw new NotSupportedException("Only writing string to PostgreSQL bitstring is supported, no reading."); + + public override async ValueTask ReadAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + => fieldDescription?.TypeModifier == 1 + ? await Read(buf, len, async, fieldDescription) + : await Read(buf, len, async, fieldDescription); + + #endregion + + #region Write + + /// + public override int ValidateAndGetLength(BitArray value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => 4 + (value.Length + 7) / 8; + + /// + public int ValidateAndGetLength(BitVector32 value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value.Data == 0 ? 4 : 8; + + /// + public int ValidateAndGetLength(bool value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => 5; + + /// + public int ValidateAndGetLength(string value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + { + if (value.Any(c => c != '0' && c != '1')) + throw new FormatException("Cannot interpret as ASCII BitString: " + value); + return 4 + (value.Length + 7) / 8; + } + + /// + public override async Task Write(BitArray value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + // Initial bitlength byte + if (buf.WriteSpaceLeft < 4) + await buf.Flush(async, cancellationToken); + buf.WriteInt32(value.Length); + + var byteLen = (value.Length + 7) / 8; + var pos = 0; + while (true) + { + var endPos = pos + Math.Min(byteLen - pos, buf.WriteSpaceLeft); + for (; pos < endPos; pos++) + { + var bitPos = pos * 8; + var b = 0; + for (var i = 0; i < Math.Min(8, value.Length - bitPos); i++) + b += (value[bitPos + i] ? 1 : 0) << (8 - i - 1); + buf.WriteByte((byte)b); + } + + if (pos == byteLen) + return; + await buf.Flush(async, cancellationToken); + } + } + + /// + public async Task Write(BitVector32 value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + if (buf.WriteSpaceLeft < 8) + await buf.Flush(async, cancellationToken); + + if (value.Data == 0) + buf.WriteInt32(0); + else + { + buf.WriteInt32(32); + buf.WriteInt32(value.Data); + } + } + + /// + public async Task Write(bool value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + if (buf.WriteSpaceLeft < 5) + await buf.Flush(async, cancellationToken); + buf.WriteInt32(1); + buf.WriteByte(value ? (byte)0x80 : (byte)0); + } + + /// + public async Task Write(string value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + // Initial bitlength byte + if (buf.WriteSpaceLeft < 4) + await buf.Flush(async, cancellationToken); + buf.WriteInt32(value.Length); + + var pos = 0; + var byteLen = (value.Length + 7) / 8; + var bytePos = 0; + + while (true) + { + var endBytePos = bytePos + Math.Min(byteLen - bytePos - 1, buf.WriteSpaceLeft); + + for (; bytePos < endBytePos; bytePos++) + { + var b = 0; + b += (value[pos++] - '0') << 7; + b += (value[pos++] - '0') << 6; + b += (value[pos++] - '0') << 5; + b += (value[pos++] - '0') << 4; + b += (value[pos++] - '0') << 3; + b += (value[pos++] - '0') << 2; + b += (value[pos++] - '0') << 1; + b += (value[pos++] - '0'); + buf.WriteByte((byte)b); + } + + if (bytePos >= byteLen - 1) + break; + await buf.Flush(async, cancellationToken); + } + + if (pos < value.Length) + { + if (buf.WriteSpaceLeft < 1) + await buf.Flush(async, cancellationToken); + + var remainder = value.Length - pos; + var lastChunk = 0; + for (var i = 7; i >= 8 - remainder; i--) + lastChunk += (value[pos++] - '0') << i; + buf.WriteByte((byte)lastChunk); + } + } + + #endregion +} + +partial class BitStringHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + BitArray converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + BitVector32 converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + Boolean converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + String converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type BitStringHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + BitArray converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + BitVector32 converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Boolean converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + String converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type BitStringHandler") + }; +} + +/// +/// A special handler for arrays of bit strings. +/// Differs from the standard array handlers in that it returns arrays of bool for BIT(1) and arrays +/// of BitArray otherwise (just like the scalar BitStringHandler does). +/// +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public class BitStringArrayHandler : ArrayHandler +{ + /// + public BitStringArrayHandler(PostgresType postgresType, BitStringHandler elementHandler, ArrayNullabilityMode arrayNullabilityMode) + : base(postgresType, elementHandler, arrayNullabilityMode) { } + + /// + protected internal override async ValueTask ReadCustom(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + { + if (ArrayTypeInfo.ElementType == typeof(BitArray)) + { + if (ArrayTypeInfo.IsArray) + return (TRequestedArray)(object)await ReadArray(buf, async); + + if (ArrayTypeInfo.IsList) + return (TRequestedArray)(object)await ReadList(buf, async); + } + + if (ArrayTypeInfo.ElementType == typeof(bool)) + { + if (ArrayTypeInfo.IsArray) + return (TRequestedArray)(object)await ReadArray(buf, async); + + if (ArrayTypeInfo.IsList) + return (TRequestedArray)(object)await ReadList(buf, async); + } + + return await base.ReadCustom(buf, len, async, fieldDescription); + } + + public override async ValueTask ReadAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + => fieldDescription?.TypeModifier == 1 + ? await ReadArray(buf, async) + : await ReadArray(buf, async); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/BoolHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/BoolHandler.cs new file mode 100644 index 0000000..b00ed76 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/BoolHandler.cs @@ -0,0 +1,57 @@ +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers; + +/// +/// A type handler for the PostgreSQL bool data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-boolean.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class BoolHandler : NpgsqlSimpleTypeHandler +{ + public BoolHandler(PostgresType pgType) : base(pgType) { } + + /// + public override bool Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => buf.ReadByte() != 0; + + /// + public override int ValidateAndGetLength(bool value, NpgsqlParameter? parameter) + => 1; + + /// + public override void Write(bool value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + => buf.WriteByte(value ? (byte)1 : (byte)0); +} + +partial class BoolHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + Boolean converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type BoolHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + Boolean converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type BoolHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/ByteaHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/ByteaHandler.cs new file mode 100644 index 0000000..4ffcde2 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/ByteaHandler.cs @@ -0,0 +1,151 @@ +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers; + +/// +/// A type handler for the PostgreSQL bytea data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-binary.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class ByteaHandler : NpgsqlTypeHandler, INpgsqlTypeHandler> +#if !NETSTANDARD2_0 + , INpgsqlTypeHandler>, INpgsqlTypeHandler> +#endif +{ + public ByteaHandler(PostgresType pgType) : base(pgType) { } + + /// + public override async ValueTask Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + { + var bytes = new byte[len]; + var pos = 0; + while (true) + { + var toRead = Math.Min(len - pos, buf.ReadBytesLeft); + buf.ReadBytes(bytes, pos, toRead); + pos += toRead; + if (pos == len) + break; + await buf.ReadMore(async); + } + return bytes; + } + + ValueTask> INpgsqlTypeHandler>.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + => throw new NotSupportedException("Only writing ArraySegment to PostgreSQL bytea is supported, no reading."); + + int ValidateAndGetLength(int bufferLen, NpgsqlParameter? parameter) + => parameter == null || parameter.Size <= 0 || parameter.Size >= bufferLen + ? bufferLen + : parameter.Size; + + /// + public override int ValidateAndGetLength(byte[] value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLength(value.Length, parameter); + + /// + public int ValidateAndGetLength(ArraySegment value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLength(value.Count, parameter); + + /// + public override Task Write(byte[] value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => Write(value, buf, 0, ValidateAndGetLength(value.Length, parameter), async, cancellationToken); + + /// + public Task Write(ArraySegment value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value.Array is null ? Task.CompletedTask : Write(value.Array, buf, value.Offset, ValidateAndGetLength(value.Count, parameter), async, cancellationToken); + + async Task Write(byte[] value, NpgsqlWriteBuffer buf, int offset, int count, bool async, CancellationToken cancellationToken = default) + { + // The entire segment fits in our buffer, copy it as usual. + if (count <= buf.WriteSpaceLeft) + { + buf.WriteBytes(value, offset, count); + return; + } + + // The segment is larger than our buffer. Flush whatever is currently in the buffer and + // write the array directly to the socket. + await buf.Flush(async, cancellationToken); + await buf.DirectWrite(new ReadOnlyMemory(value, offset, count), async, cancellationToken); + } + +#if !NETSTANDARD2_0 + /// + public int ValidateAndGetLength(Memory value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLength(value.Length, parameter); + + /// + public int ValidateAndGetLength(ReadOnlyMemory value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLength(value.Length, parameter); + + /// + public async Task Write(ReadOnlyMemory value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + if (parameter != null && parameter.Size > 0 && parameter.Size < value.Length) + value = value.Slice(0, parameter.Size); + + // The entire segment fits in our buffer, copy it into the buffer as usual. + if (value.Length <= buf.WriteSpaceLeft) + { + buf.WriteBytes(value.Span); + return; + } + + // The segment is larger than our buffer. Perform a direct write, flushing whatever is currently in the buffer + // and then writing the array directly to the socket. + await buf.DirectWrite(value, async, cancellationToken); + } + + /// + public Task Write(Memory value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => Write((ReadOnlyMemory)value, buf, lengthCache, parameter, async, cancellationToken); + + ValueTask> INpgsqlTypeHandler>.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescriptioncancellationToken) + => throw new NotSupportedException("Only writing ReadOnlyMemory to PostgreSQL bytea is supported, no reading."); + + ValueTask> INpgsqlTypeHandler>.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + => throw new NotSupportedException("Only writing Memory to PostgreSQL bytea is supported, no reading."); +#endif + + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + Byte[] converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + ArraySegment converted => ((INpgsqlTypeHandler>)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + ReadOnlyMemory converted => ((INpgsqlTypeHandler>)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + Memory converted => ((INpgsqlTypeHandler>)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type ByteaHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + Byte[] converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + ArraySegment converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + ReadOnlyMemory converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Memory converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type ByteaHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/ByReference.cs b/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/ByReference.cs new file mode 100644 index 0000000..e5f02bd --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/ByReference.cs @@ -0,0 +1,10 @@ + +// Only used for value types, but can't constrain because MappedCompositeHandler isn't constrained +#nullable disable + +namespace Npgsql.Internal.TypeHandlers.CompositeHandlers; + +sealed class ByReference +{ + public T Value; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/CompositeConstructorHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/CompositeConstructorHandler.cs new file mode 100644 index 0000000..b1b6337 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/CompositeConstructorHandler.cs @@ -0,0 +1,62 @@ +using System; +using System.Reflection; +using System.Threading.Tasks; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers.CompositeHandlers; + +class CompositeConstructorHandler +{ + public PostgresType PostgresType { get; } + public ConstructorInfo ConstructorInfo { get; } + public CompositeParameterHandler[] Handlers { get; } + + protected CompositeConstructorHandler(PostgresType postgresType, ConstructorInfo constructorInfo, CompositeParameterHandler[] handlers) + { + PostgresType = postgresType; + ConstructorInfo = constructorInfo; + Handlers = handlers; + } + + public virtual async ValueTask Read(NpgsqlReadBuffer buffer, bool async) + { + await buffer.Ensure(sizeof(int), async); + + var fieldCount = buffer.ReadInt32(); + if (fieldCount != Handlers.Length) + throw new InvalidOperationException($"pg_attributes contains {Handlers.Length} fields for type {PostgresType.DisplayName}, but {fieldCount} fields were received."); + + var args = new object?[Handlers.Length]; + foreach (var handler in Handlers) + args[handler.ParameterPosition] = await handler.Read(buffer, async); + + return (TComposite)ConstructorInfo.Invoke(args); + } + + public static CompositeConstructorHandler Create(PostgresType postgresType, ConstructorInfo constructorInfo, CompositeParameterHandler[] parameterHandlers) + { + const int maxGenericParameters = 8; + + if (parameterHandlers.Length > maxGenericParameters) + return new CompositeConstructorHandler(postgresType, constructorInfo, parameterHandlers); + + var parameterTypes = new Type[1 + maxGenericParameters]; + foreach (var parameterHandler in parameterHandlers) + parameterTypes[1 + parameterHandler.ParameterPosition] = parameterHandler.ParameterType; + + for (var parameterIndex = 1; parameterIndex < parameterTypes.Length; parameterIndex++) + parameterTypes[parameterIndex] ??= typeof(Unused); + + parameterTypes[0] = typeof(TComposite); + return (CompositeConstructorHandler)Activator.CreateInstance( + typeof(CompositeConstructorHandler<,,,,,,,,>).MakeGenericType(parameterTypes), + BindingFlags.Instance | BindingFlags.Public, + binder: null, + args: new object[] { postgresType, constructorInfo, parameterHandlers }, + culture: null)!; + } + + readonly struct Unused + { + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/CompositeConstructorHandler`.cs b/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/CompositeConstructorHandler`.cs new file mode 100644 index 0000000..b7d8a7b --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/CompositeConstructorHandler`.cs @@ -0,0 +1,66 @@ +using System; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading.Tasks; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers.CompositeHandlers; + +sealed class CompositeConstructorHandler : CompositeConstructorHandler +{ + delegate TComposite CompositeConstructor(in Arguments args); + + readonly CompositeConstructor _constructor; + + public CompositeConstructorHandler(PostgresType postgresType, ConstructorInfo constructorInfo, CompositeParameterHandler[] parameterHandlers) + : base(postgresType, constructorInfo, parameterHandlers) + { + var parameter = Expression.Parameter(typeof(Arguments).MakeByRefType()); + var fields = Enumerable + .Range(1, parameterHandlers.Length) + .Select(i => Expression.Field(parameter, "Argument" + i)); + + _constructor = Expression + .Lambda(Expression.New(constructorInfo, fields), parameter) + .Compile(); + } + + public override async ValueTask Read(NpgsqlReadBuffer buffer, bool async) + { + await buffer.Ensure(sizeof(int), async); + + var fieldCount = buffer.ReadInt32(); + if (fieldCount != Handlers.Length) + throw new InvalidOperationException($"pg_attributes contains {Handlers.Length} fields for type {PostgresType.DisplayName}, but {fieldCount} fields were received."); + + var args = default(Arguments); + + foreach (var handler in Handlers) + switch (handler.ParameterPosition) + { + case 0: args.Argument1 = await handler.Read(buffer, async); break; + case 1: args.Argument2 = await handler.Read(buffer, async); break; + case 2: args.Argument3 = await handler.Read(buffer, async); break; + case 3: args.Argument4 = await handler.Read(buffer, async); break; + case 4: args.Argument5 = await handler.Read(buffer, async); break; + case 5: args.Argument6 = await handler.Read(buffer, async); break; + case 6: args.Argument7 = await handler.Read(buffer, async); break; + case 7: args.Argument8 = await handler.Read(buffer, async); break; + } + + return _constructor(args); + } + + struct Arguments + { + public T1 Argument1; + public T2 Argument2; + public T3 Argument3; + public T4 Argument4; + public T5 Argument5; + public T6 Argument6; + public T7 Argument7; + public T8 Argument8; + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/CompositeHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/CompositeHandler.cs new file mode 100644 index 0000000..957a2d3 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/CompositeHandler.cs @@ -0,0 +1,306 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using Npgsql.TypeMapping; +using NpgsqlTypes; + +#region Trimming warning suppressions + +[module: UnconditionalSuppressMessage( + "Composite type mapping currently isn't trimming-safe, and warnings are generated at the MapComposite level.", + "IL2046", Scope = "type", Target = "Npgsql.Internal.TypeHandlers.CompositeHandlers.CompositeHandler")] +[module: UnconditionalSuppressMessage( + "Composite type mapping currently isn't trimming-safe, and warnings are generated at the MapComposite level.", + "IL2080", Scope = "type", Target = "Npgsql.Internal.TypeHandlers.CompositeHandlers.CompositeHandler")] +[module: UnconditionalSuppressMessage( + "Composite type mapping currently isn't trimming-safe, and warnings are generated at the MapComposite level.", + "IL2026", Scope = "type", Target = "Npgsql.Internal.TypeHandlers.CompositeHandlers.CompositeHandler")] +[module: UnconditionalSuppressMessage( + "Composite type mapping currently isn't trimming-safe, and warnings are generated at the MapComposite level.", + "IL2090", Scope = "type", Target = "Npgsql.Internal.TypeHandlers.CompositeHandlers.CompositeHandler")] +[module: UnconditionalSuppressMessage( + "Composite type mapping currently isn't trimming-safe, and warnings are generated at the MapComposite level.", + "IL2087", Scope = "type", Target = "Npgsql.Internal.TypeHandlers.CompositeHandlers.CompositeHandler")] +[module: UnconditionalSuppressMessage( + "Composite type mapping currently isn't trimming-safe, and warnings are generated at the MapComposite level.", + "IL2055", Scope = "type", Target = "Npgsql.Internal.TypeHandlers.CompositeHandlers.CompositeHandler")] +[module: UnconditionalSuppressMessage( + "Composite type mapping currently isn't trimming-safe, and warnings are generated at the MapComposite level.", + "IL2077", Scope = "type", Target = "Npgsql.Internal.TypeHandlers.CompositeHandlers.CompositeHandler")] + +#endregion + +namespace Npgsql.Internal.TypeHandlers.CompositeHandlers; + +partial class CompositeHandler : NpgsqlTypeHandler, ICompositeHandler +{ + readonly ConnectorTypeMapper _typeMapper; + readonly INpgsqlNameTranslator _nameTranslator; + + Func? _constructor; + CompositeConstructorHandler? _constructorHandler; + CompositeMemberHandler[] _memberHandlers = null!; + + public Type CompositeType => typeof(T); + + public CompositeHandler(PostgresCompositeType postgresType, ConnectorTypeMapper typeMapper, INpgsqlNameTranslator nameTranslator) + : base(postgresType) + { + _typeMapper = typeMapper; + _nameTranslator = nameTranslator; + } + + public override ValueTask Read(NpgsqlReadBuffer buffer, int length, bool async, FieldDescription? fieldDescription = null) + { + Initialize(); + + return _constructorHandler is null + ? ReadUsingMemberHandlers(buffer, async) + : _constructorHandler.Read(buffer, async); + + async ValueTask ReadUsingMemberHandlers(NpgsqlReadBuffer buffer, bool async) + { + await buffer.Ensure(sizeof(int), async); + + var fieldCount = buffer.ReadInt32(); + if (fieldCount != _memberHandlers.Length) + throw new InvalidOperationException($"pg_attributes contains {_memberHandlers.Length} fields for type {PgDisplayName}, but {fieldCount} fields were received."); + + if (IsValueType.Value) + { + var composite = new ByReference { Value = _constructor!() }; + foreach (var member in _memberHandlers) + await member.Read(composite, buffer, async); + + return composite.Value; + } + else + { + var composite = _constructor!(); + foreach (var member in _memberHandlers) + await member.Read(composite, buffer, async); + + return composite; + } + } + } + + public override async Task Write(T value, NpgsqlWriteBuffer buffer, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + Initialize(); + + if (buffer.WriteSpaceLeft < sizeof(int)) + await buffer.Flush(async, cancellationToken); + + buffer.WriteInt32(_memberHandlers.Length); + + foreach (var member in _memberHandlers) + await member.Write(value, buffer, lengthCache, async, cancellationToken); + } + + public override int ValidateAndGetLength(T value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + { + Initialize(); + + lengthCache ??= new NpgsqlLengthCache(1); + if (lengthCache.IsPopulated) + return lengthCache.Get(); + + // Leave empty slot for the entire composite type, and go ahead an populate the element slots + var position = lengthCache.Position; + lengthCache.Set(0); + + // number of fields + (type oid + field length) * member count + var length = sizeof(int) + sizeof(int) * 2 * _memberHandlers.Length; + foreach (var member in _memberHandlers) + length += member.ValidateAndGetLength(value, ref lengthCache); + + return lengthCache.Lengths[position] = length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void Initialize() + { + if (_memberHandlers is null) + InitializeCore(); + + void InitializeCore() + { + var pgType = (PostgresCompositeType)PostgresType; + + _memberHandlers = CreateMemberHandlers(pgType, _typeMapper, _nameTranslator); + _constructorHandler = CreateConstructorHandler(pgType, _typeMapper, _nameTranslator); + _constructor = _constructorHandler is null + ? Expression + .Lambda>(Expression.New(typeof(T))) + .Compile() + : null; + } + } + + static CompositeConstructorHandler? CreateConstructorHandler(PostgresCompositeType pgType, ConnectorTypeMapper typeMapper, INpgsqlNameTranslator nameTranslator) + { + var pgFields = pgType.Fields; + var clrType = typeof(T); + + ConstructorInfo? clrDefaultConstructor = null; + + foreach (var clrConstructor in clrType.GetConstructors()) + { + var clrParameters = clrConstructor.GetParameters(); + if (clrParameters.Length != pgFields.Count) + { + if (clrParameters.Length == 0) + clrDefaultConstructor = clrConstructor; + + continue; + } + + var clrParameterHandlerCount = 0; + var clrParametersMapped = new ParameterInfo[pgFields.Count]; + + foreach (var clrParameter in clrParameters) + { + var attr = clrParameter.GetCustomAttribute(); + var name = attr?.PgName ?? (clrParameter.Name is string clrName ? nameTranslator.TranslateMemberName(clrName) : null); + if (name is null) + break; + + for (var pgFieldIndex = pgFields.Count - 1; pgFieldIndex >= 0; --pgFieldIndex) + { + var pgField = pgFields[pgFieldIndex]; + if (pgField.Name != name) + continue; + + if (clrParametersMapped[pgFieldIndex] != null) + throw new AmbiguousMatchException($"Multiple constructor parameters are mapped to the '{pgField.Name}' field."); + + clrParameterHandlerCount++; + clrParametersMapped[pgFieldIndex] = clrParameter; + + break; + } + } + + if (clrParameterHandlerCount < pgFields.Count) + continue; + + var clrParameterHandlers = new CompositeParameterHandler[pgFields.Count]; + for (var pgFieldIndex = 0; pgFieldIndex < pgFields.Count; ++pgFieldIndex) + { + var pgField = pgFields[pgFieldIndex]; + + if (!typeMapper.TryResolveByOID(pgField.Type.OID, out var handler)) + throw new NpgsqlException($"PostgreSQL composite type {pgType.DisplayName} has field {pgField.Type.DisplayName} with an unknown type (OID = {pgField.Type.OID})."); + + var clrParameter = clrParametersMapped[pgFieldIndex]; + var clrParameterHandlerType = typeof(CompositeParameterHandler<>) + .MakeGenericType(clrParameter.ParameterType); + + clrParameterHandlers[pgFieldIndex] = (CompositeParameterHandler)Activator.CreateInstance( + clrParameterHandlerType, + BindingFlags.Instance | BindingFlags.Public, + binder: null, + args: new object[] { handler, clrParameter }, + culture: null)!; + } + + return CompositeConstructorHandler.Create(pgType, clrConstructor, clrParameterHandlers); + } + + if (clrDefaultConstructor is null && !clrType.IsValueType) + throw new InvalidOperationException($"No parameterless constructor defined for type '{clrType}'."); + + return null; + } + + static CompositeMemberHandler[] CreateMemberHandlers(PostgresCompositeType pgType, ConnectorTypeMapper typeMapper, INpgsqlNameTranslator nameTranslator) + { + var pgFields = pgType.Fields; + + var clrType = typeof(T); + var clrMemberHandlers = new CompositeMemberHandler[pgFields.Count]; + var clrMemberHandlerCount = 0; + var clrMemberHandlerType = IsValueType.Value + ? typeof(CompositeStructMemberHandler<,>) + : typeof(CompositeClassMemberHandler<,>); + + foreach (var clrProperty in clrType.GetProperties(BindingFlags.Instance | BindingFlags.Public)) + CreateMemberHandler(clrProperty, clrProperty.PropertyType); + + foreach (var clrField in clrType.GetFields(BindingFlags.Instance | BindingFlags.Public)) + CreateMemberHandler(clrField, clrField.FieldType); + + if (clrMemberHandlerCount != pgFields.Count) + { + var notMappedFields = string.Join(", ", clrMemberHandlers + .Select((member, memberIndex) => member == null ? $"'{pgFields[memberIndex].Name}'" : null) + .Where(member => member != null)); + throw new InvalidOperationException($"PostgreSQL composite type {pgType.DisplayName} contains fields {notMappedFields} which could not match any on CLR type {clrType.Name}"); + } + + return clrMemberHandlers; + + void CreateMemberHandler(MemberInfo clrMember, Type clrMemberType) + { + var attr = clrMember.GetCustomAttribute(); + var name = attr?.PgName ?? nameTranslator.TranslateMemberName(clrMember.Name); + + for (var pgFieldIndex = pgFields.Count - 1; pgFieldIndex >= 0; --pgFieldIndex) + { + var pgField = pgFields[pgFieldIndex]; + if (pgField.Name != name) + continue; + + if (clrMemberHandlers[pgFieldIndex] != null) + throw new AmbiguousMatchException($"Multiple class members are mapped to the '{pgField.Name}' field."); + + if (!typeMapper.TryResolveByOID(pgField.Type.OID, out var handler)) + throw new NpgsqlException($"PostgreSQL composite type {pgType.DisplayName} has field {pgField.Type.DisplayName} with an unknown type (OID = {pgField.Type.OID})."); + + clrMemberHandlerCount++; + clrMemberHandlers[pgFieldIndex] = (CompositeMemberHandler)Activator.CreateInstance( + clrMemberHandlerType.MakeGenericType(clrType, clrMemberType), + BindingFlags.Instance | BindingFlags.Public, + binder: null, + args: new object[] { clrMember, pgField.Type, handler }, + culture: null)!; + + break; + } + } + } +} + +partial class CompositeHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + T converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type CompositeHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + T converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type CompositeHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/CompositeMemberHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/CompositeMemberHandler.cs new file mode 100644 index 0000000..48d57e9 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/CompositeMemberHandler.cs @@ -0,0 +1,28 @@ +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers.CompositeHandlers; + +abstract class CompositeMemberHandler +{ + public MemberInfo MemberInfo { get; } + public PostgresType PostgresType { get; } + + protected CompositeMemberHandler(MemberInfo memberInfo, PostgresType postgresType) + { + MemberInfo = memberInfo; + PostgresType = postgresType; + } + + public abstract ValueTask Read(TComposite composite, NpgsqlReadBuffer buffer, bool async); + + public abstract ValueTask Read(ByReference composite, NpgsqlReadBuffer buffer, bool async); + + public abstract Task Write(TComposite composite, NpgsqlWriteBuffer buffer, NpgsqlLengthCache? lengthCache, bool async, CancellationToken cancellationToken = default); + + public abstract int ValidateAndGetLength(TComposite composite, [NotNullIfNotNull("lengthCache")] ref NpgsqlLengthCache? lengthCache); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/CompositeMemberHandlerOfClass.cs b/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/CompositeMemberHandlerOfClass.cs new file mode 100644 index 0000000..0593e4d --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/CompositeMemberHandlerOfClass.cs @@ -0,0 +1,105 @@ +using System; +using System.Diagnostics; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers.CompositeHandlers; + +sealed class CompositeClassMemberHandler : CompositeMemberHandler + where TComposite : class +{ + delegate TMember GetMember(TComposite composite); + delegate void SetMember(TComposite composite, TMember value); + + readonly GetMember? _get; + readonly SetMember? _set; + readonly NpgsqlTypeHandler _handler; + + public CompositeClassMemberHandler(FieldInfo fieldInfo, PostgresType postgresType, NpgsqlTypeHandler handler) + : base(fieldInfo, postgresType) + { + var composite = Expression.Parameter(typeof(TComposite), "composite"); + var value = Expression.Parameter(typeof(TMember), "value"); + + _get = Expression + .Lambda(Expression.Field(composite, fieldInfo), composite) + .Compile(); + _set = Expression + .Lambda(Expression.Assign(Expression.Field(composite, fieldInfo), value), composite, value) + .Compile(); + _handler = handler; + } + + public CompositeClassMemberHandler(PropertyInfo propertyInfo, PostgresType postgresType, NpgsqlTypeHandler handler) + : base(propertyInfo, postgresType) + { + var getMethod = propertyInfo.GetGetMethod(); + if (getMethod != null) + _get = (GetMember)Delegate.CreateDelegate(typeof(GetMember), getMethod); + + var setMethod = propertyInfo.GetSetMethod(); + if (setMethod != null) + _set = (SetMember)Delegate.CreateDelegate(typeof(SetMember), setMethod); + + Debug.Assert(setMethod != null || getMethod != null); + + _handler = handler; + } + + public override async ValueTask Read(TComposite composite, NpgsqlReadBuffer buffer, bool async) + { + if (_set == null) + ThrowHelper.ThrowInvalidOperationException_NoPropertySetter(typeof(TComposite), MemberInfo); + + await buffer.Ensure(sizeof(uint) + sizeof(int), async); + + var oid = buffer.ReadUInt32(); + Debug.Assert(oid == PostgresType.OID); + + var length = buffer.ReadInt32(); + if (length == -1) + return; + + var value = NullableHandler.Exists + ? await NullableHandler.ReadAsync(_handler, buffer, length, async) + : await _handler.Read(buffer, length, async); + + _set(composite, value); + } + + public override ValueTask Read(ByReference composite, NpgsqlReadBuffer buffer, bool async) + => throw new NotSupportedException(); + + public override async Task Write(TComposite composite, NpgsqlWriteBuffer buffer, NpgsqlLengthCache? lengthCache, bool async, CancellationToken cancellationToken = default) + { + if (_get == null) + ThrowHelper.ThrowInvalidOperationException_NoPropertyGetter(typeof(TComposite), MemberInfo); + + if (buffer.WriteSpaceLeft < sizeof(int)) + await buffer.Flush(async, cancellationToken); + + buffer.WriteUInt32(PostgresType.OID); + if (NullableHandler.Exists) + await NullableHandler.WriteAsync(_handler, _get(composite), buffer, lengthCache, null, async, cancellationToken); + else + await _handler.WriteWithLength(_get(composite), buffer, lengthCache, null, async, cancellationToken); + } + + public override int ValidateAndGetLength(TComposite composite, ref NpgsqlLengthCache? lengthCache) + { + if (_get == null) + ThrowHelper.ThrowInvalidOperationException_NoPropertyGetter(typeof(TComposite), MemberInfo); + + var value = _get(composite); + if (value is null) + return 0; + + return NullableHandler.Exists + ? NullableHandler.ValidateAndGetLength(_handler, value, ref lengthCache, null) + : _handler.ValidateAndGetLength(value, ref lengthCache, null); + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/CompositeMemberHandlerOfStruct.cs b/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/CompositeMemberHandlerOfStruct.cs new file mode 100644 index 0000000..2fa1d48 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/CompositeMemberHandlerOfStruct.cs @@ -0,0 +1,109 @@ +using System; +using System.Diagnostics; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers.CompositeHandlers; + +sealed class CompositeStructMemberHandler : CompositeMemberHandler + where TComposite : struct +{ + delegate TMember GetMember(ref TComposite composite); + delegate void SetMember(ref TComposite composite, TMember value); + + readonly GetMember? _get; + readonly SetMember? _set; + readonly NpgsqlTypeHandler _handler; + + public CompositeStructMemberHandler(FieldInfo fieldInfo, PostgresType postgresType, NpgsqlTypeHandler handler) + : base(fieldInfo, postgresType) + { + var composite = Expression.Parameter(typeof(TComposite).MakeByRefType(), "composite"); + var value = Expression.Parameter(typeof(TMember), "value"); + + _get = Expression + .Lambda(Expression.Field(composite, fieldInfo), composite) + .Compile(); + _set = Expression + .Lambda(Expression.Assign(Expression.Field(composite, fieldInfo), value), composite, value) + .Compile(); + _handler = handler; + } + + public CompositeStructMemberHandler(PropertyInfo propertyInfo, PostgresType postgresType, NpgsqlTypeHandler handler) + : base(propertyInfo, postgresType) + { + var getMethod = propertyInfo.GetGetMethod(); + if (getMethod != null) + _get = (GetMember)Delegate.CreateDelegate(typeof(GetMember), getMethod); + + var setMethod = propertyInfo.GetSetMethod(); + if (setMethod != null) + _set = (SetMember)Delegate.CreateDelegate(typeof(SetMember), setMethod); + + Debug.Assert(setMethod != null || getMethod != null); + + _handler = handler; + } + + public override ValueTask Read(TComposite composite, NpgsqlReadBuffer buffer, bool async) + => throw new NotSupportedException(); + + public override async ValueTask Read(ByReference composite, NpgsqlReadBuffer buffer, bool async) + { + if (_set == null) + ThrowHelper.ThrowInvalidOperationException_NoPropertySetter(typeof(TComposite), MemberInfo); + + await buffer.Ensure(sizeof(uint) + sizeof(int), async); + + var oid = buffer.ReadUInt32(); + Debug.Assert(oid == PostgresType.OID); + + var length = buffer.ReadInt32(); + if (length == -1) + return; + + var value = NullableHandler.Exists + ? await NullableHandler.ReadAsync(_handler, buffer, length, async) + : await _handler.Read(buffer, length, async); + + Set(composite, value); + } + + public override async Task Write(TComposite composite, NpgsqlWriteBuffer buffer, NpgsqlLengthCache? lengthCache, bool async, CancellationToken cancellationToken = default) + { + if (_get == null) + ThrowHelper.ThrowInvalidOperationException_NoPropertyGetter(typeof(TComposite), MemberInfo); + + if (buffer.WriteSpaceLeft < sizeof(int)) + await buffer.Flush(async, cancellationToken); + + buffer.WriteUInt32(PostgresType.OID); + await (NullableHandler.Exists + ? NullableHandler.WriteAsync(_handler, _get(ref composite), buffer, lengthCache, null, async, cancellationToken) + : _handler.WriteWithLength(_get(ref composite), buffer, lengthCache, null, async, cancellationToken)); + } + + public override int ValidateAndGetLength(TComposite composite, ref NpgsqlLengthCache? lengthCache) + { + if (_get == null) + ThrowHelper.ThrowInvalidOperationException_NoPropertyGetter(typeof(TComposite), MemberInfo); + + var value = _get(ref composite); + if (value is null) + return 0; + + return NullableHandler.Exists + ? NullableHandler.ValidateAndGetLength(_handler, value, ref lengthCache, null) + : _handler.ValidateAndGetLength(value, ref lengthCache, null); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void Set(ByReference composite, TMember value) + => _set!(ref composite.Value, value); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/CompositeParameterHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/CompositeParameterHandler.cs new file mode 100644 index 0000000..f99de18 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/CompositeParameterHandler.cs @@ -0,0 +1,36 @@ +using System; +using System.Reflection; +using System.Threading.Tasks; +using Npgsql.Internal.TypeHandling; + +namespace Npgsql.Internal.TypeHandlers.CompositeHandlers; + +abstract class CompositeParameterHandler +{ + public NpgsqlTypeHandler Handler { get; } + public Type ParameterType { get; } + public int ParameterPosition { get; } + + public CompositeParameterHandler(NpgsqlTypeHandler handler, ParameterInfo parameterInfo) + { + Handler = handler; + ParameterType = parameterInfo.ParameterType; + ParameterPosition = parameterInfo.Position; + } + + public async ValueTask Read(NpgsqlReadBuffer buffer, bool async) + { + await buffer.Ensure(sizeof(uint) + sizeof(int), async); + + var oid = buffer.ReadUInt32(); + var length = buffer.ReadInt32(); + if (length == -1) + return default!; + + return NullableHandler.Exists + ? await NullableHandler.ReadAsync(Handler, buffer, length, async) + : await Handler.Read(buffer, length, async); + } + + public abstract ValueTask Read(NpgsqlReadBuffer buffer, bool async); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/CompositeParameterHandler`.cs b/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/CompositeParameterHandler`.cs new file mode 100644 index 0000000..6c2d9da --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/CompositeParameterHandler`.cs @@ -0,0 +1,21 @@ +using System.Reflection; +using System.Threading.Tasks; +using Npgsql.Internal.TypeHandling; + +namespace Npgsql.Internal.TypeHandlers.CompositeHandlers; + +sealed class CompositeParameterHandler : CompositeParameterHandler +{ + public CompositeParameterHandler(NpgsqlTypeHandler handler, ParameterInfo parameterInfo) + : base(handler, parameterInfo) { } + + public override ValueTask Read(NpgsqlReadBuffer buffer, bool async) + { + var task = Read(buffer, async); + return task.IsCompleted + ? new ValueTask(task.Result) + : AwaitTask(task); + + static async ValueTask AwaitTask(ValueTask task) => await task; + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/ICompositeHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/ICompositeHandler.cs new file mode 100644 index 0000000..5bb1862 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/ICompositeHandler.cs @@ -0,0 +1,11 @@ +using System; + +namespace Npgsql.Internal.TypeHandlers.CompositeHandlers; + +interface ICompositeHandler +{ + /// + /// The CLR type mapped to the PostgreSQL composite type. + /// + Type CompositeType { get; } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/IsValueType.cs b/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/IsValueType.cs new file mode 100644 index 0000000..360cae9 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/CompositeHandlers/IsValueType.cs @@ -0,0 +1,6 @@ +namespace Npgsql.Internal.TypeHandlers.CompositeHandlers; + +static class IsValueType +{ + public static readonly bool Value = typeof(T).IsValueType; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/DateTimeHandlers/DateHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/DateTimeHandlers/DateHandler.cs new file mode 100644 index 0000000..4152c6f --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/DateTimeHandlers/DateHandler.cs @@ -0,0 +1,195 @@ +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using NpgsqlTypes; +using static Npgsql.Util.Statics; + +#pragma warning disable 618 // NpgsqlDate is obsolete, remove in 7.0 + +namespace Npgsql.Internal.TypeHandlers.DateTimeHandlers; + +/// +/// A type handler for the PostgreSQL date data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-datetime.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class DateHandler : NpgsqlSimpleTypeHandlerWithPsv, + INpgsqlSimpleTypeHandler +#if NET6_0_OR_GREATER + , INpgsqlSimpleTypeHandler +#endif +{ + static readonly DateTime BaseValueDateTime = new(2000, 1, 1, 0, 0, 0); + + /// + /// Constructs a + /// + public DateHandler(PostgresType postgresType) : base(postgresType) { } + + #region Read + + /// + public override DateTime Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + { + var npgsqlDate = ReadPsv(buf, len, fieldDescription); + + if (npgsqlDate.IsFinite) + return (DateTime)npgsqlDate; + if (DisableDateTimeInfinityConversions) + throw new InvalidCastException("Can't convert infinite date values to DateTime"); + if (npgsqlDate.IsInfinity) + return DateTime.MaxValue; + return DateTime.MinValue; + } + + /// + /// Copied wholesale from Postgresql backend/utils/adt/datetime.c:j2date + /// + protected override NpgsqlDate ReadPsv(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + { + var binDate = buf.ReadInt32(); + + return binDate switch + { + int.MaxValue => NpgsqlDate.Infinity, + int.MinValue => NpgsqlDate.NegativeInfinity, + _ => new NpgsqlDate(binDate + 730119) + }; + } + + int INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => buf.ReadInt32(); + + #endregion Read + + #region Write + + /// + public override int ValidateAndGetLength(DateTime value, NpgsqlParameter? parameter) => 4; + + /// + public override int ValidateAndGetLength(NpgsqlDate value, NpgsqlParameter? parameter) => 4; + + /// + public int ValidateAndGetLength(int value, NpgsqlParameter? parameter) => 4; + + /// + public override void Write(DateTime value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + { + if (!DisableDateTimeInfinityConversions) + { + if (value == DateTime.MaxValue) + { + Write(NpgsqlDate.Infinity, buf, parameter); + return; + } + + if (value == DateTime.MinValue) + { + Write(NpgsqlDate.NegativeInfinity, buf, parameter); + return; + } + } + + Write(new NpgsqlDate(value), buf, parameter); + } + + /// + public override void Write(NpgsqlDate value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + { + if (value == NpgsqlDate.NegativeInfinity) + buf.WriteInt32(int.MinValue); + else if (value == NpgsqlDate.Infinity) + buf.WriteInt32(int.MaxValue); + else + buf.WriteInt32(value.DaysSinceEra - 730119); + } + + /// + public void Write(int value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + => buf.WriteInt32(value); + + #endregion Write + +#if NET6_0_OR_GREATER + DateOnly INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + { + var npgsqlDate = ReadPsv(buf, len, fieldDescription); + + if (npgsqlDate.IsFinite) + return (DateOnly)npgsqlDate; + if (DisableDateTimeInfinityConversions) + throw new InvalidCastException(NpgsqlStrings.CannotReadInfinityValue); + if (npgsqlDate.IsInfinity) + return DateOnly.MaxValue; + return DateOnly.MinValue; + } + + public int ValidateAndGetLength(DateOnly value, NpgsqlParameter? parameter) => 4; + + public void Write(DateOnly value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + { + if (!DisableDateTimeInfinityConversions) + { + if (value == DateOnly.MaxValue) + { + Write(NpgsqlDate.Infinity, buf, parameter); + return; + } + + if (value == DateOnly.MinValue) + { + Write(NpgsqlDate.NegativeInfinity, buf, parameter); + return; + } + } + + Write(new NpgsqlDate(value), buf, parameter); + } + + public override NpgsqlTypeHandler CreateRangeHandler(PostgresType pgRangeType) + => new RangeHandler(pgRangeType, this); + + public override NpgsqlTypeHandler CreateMultirangeHandler(PostgresMultirangeType pgRangeType) + => new MultirangeHandler(pgRangeType, new RangeHandler(pgRangeType, this)); +#endif + + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + DateTime converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + NpgsqlDate converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + Int32 converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + DateOnly converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type DateHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + DateTime converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + NpgsqlDate converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Int32 converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + DateOnly converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type DateHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/DateTimeHandlers/DateTimeUtils.cs b/LibExternal/Npgsql/Internal/TypeHandlers/DateTimeHandlers/DateTimeUtils.cs new file mode 100644 index 0000000..f3099b1 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/DateTimeHandlers/DateTimeUtils.cs @@ -0,0 +1,131 @@ +using System; +using System.Runtime.CompilerServices; +using Npgsql.BackendMessages; +using NpgsqlTypes; +using static Npgsql.Util.Statics; + +namespace Npgsql.Internal.TypeHandlers.DateTimeHandlers; + +static class DateTimeUtils +{ + const long PostgresTimestampOffsetTicks = 630822816000000000L; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static DateTime DecodeTimestamp(long value, DateTimeKind kind) + => new(value * 10 + PostgresTimestampOffsetTicks, kind); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static long EncodeTimestamp(DateTime value) + // Rounding here would cause problems because we would round up DateTime.MaxValue + // which would make it impossible to retrieve it back from the database, so we just drop the additional precision + => (value.Ticks - PostgresTimestampOffsetTicks) / 10; + + internal static DateTime ReadDateTime(NpgsqlReadBuffer buf, DateTimeKind kind) + { + try + { + return buf.ReadInt64() switch + { + long.MaxValue => DisableDateTimeInfinityConversions + ? throw new InvalidCastException(NpgsqlStrings.CannotReadInfinityValue) + : DateTime.MaxValue, + long.MinValue => DisableDateTimeInfinityConversions + ? throw new InvalidCastException(NpgsqlStrings.CannotReadInfinityValue) + : DateTime.MinValue, + var value => DecodeTimestamp(value, kind) + }; + } + catch (ArgumentOutOfRangeException e) + { + throw new InvalidCastException("Out of the range of DateTime (year must be between 1 and 9999)", e); + } + } + +#pragma warning disable 618 // NpgsqlDateTime is obsolete, remove in 7.0 + internal static NpgsqlDateTime ReadNpgsqlDateTime(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + { + var value = buf.ReadInt64(); + if (value == long.MaxValue) + return NpgsqlDateTime.Infinity; + if (value == long.MinValue) + return NpgsqlDateTime.NegativeInfinity; + if (value >= 0) + { + var date = (int)(value / 86400000000L); + var time = value % 86400000000L; + + date += 730119; // 730119 = days since era (0001-01-01) for 2000-01-01 + time *= 10; // To 100ns + + return new NpgsqlDateTime(new NpgsqlDate(date), new TimeSpan(time)); + } + else + { + value = -value; + var date = (int)(value / 86400000000L); + var time = value % 86400000000L; + if (time != 0) + { + ++date; + time = 86400000000L - time; + } + + date = 730119 - date; // 730119 = days since era (0001-01-01) for 2000-01-01 + time *= 10; // To 100ns + + return new NpgsqlDateTime(new NpgsqlDate(date), new TimeSpan(time)); + } + } +#pragma warning restore 618 + + internal static void WriteTimestamp(DateTime value, NpgsqlWriteBuffer buf) + { + if (!DisableDateTimeInfinityConversions) + { + if (value == DateTime.MaxValue) + { + buf.WriteInt64(long.MaxValue); + return; + } + + if (value == DateTime.MinValue) + { + buf.WriteInt64(long.MinValue); + return; + } + } + + var postgresTimestamp = EncodeTimestamp(value); + buf.WriteInt64(postgresTimestamp); + } + +#pragma warning disable 618 // NpgsqlDateTime is obsolete, remove in 7.0 + internal static void WriteTimestamp(NpgsqlDateTime value, NpgsqlWriteBuffer buf) + { + if (value.IsInfinity) + { + buf.WriteInt64(long.MaxValue); + return; + } + + if (value.IsNegativeInfinity) + { + buf.WriteInt64(long.MinValue); + return; + } + + var uSecsTime = value.Time.Ticks / 10; + + if (value >= new NpgsqlDateTime(2000, 1, 1, 0, 0, 0)) + { + var uSecsDate = (value.Date.DaysSinceEra - 730119) * 86400000000L; + buf.WriteInt64(uSecsDate + uSecsTime); + } + else + { + var uSecsDate = (730119 - value.Date.DaysSinceEra) * 86400000000L; + buf.WriteInt64(uSecsTime - uSecsDate); + } + } +#pragma warning restore 618 +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/DateTimeHandlers/IntervalHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/DateTimeHandlers/IntervalHandler.cs new file mode 100644 index 0000000..e824a31 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/DateTimeHandlers/IntervalHandler.cs @@ -0,0 +1,110 @@ +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using NpgsqlTypes; + +#pragma warning disable 618 // NpgsqlTimeSpan is obsolete, remove in 7.0 + +namespace Npgsql.Internal.TypeHandlers.DateTimeHandlers; + +/// +/// A type handler for the PostgreSQL date interval type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-datetime.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class IntervalHandler : NpgsqlSimpleTypeHandlerWithPsv, + INpgsqlSimpleTypeHandler +{ + /// + /// Constructs an + /// + public IntervalHandler(PostgresType postgresType) : base(postgresType) { } + + /// + public override TimeSpan Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => (TimeSpan)((INpgsqlSimpleTypeHandler)this).Read(buf, len, fieldDescription); + + /// + protected override NpgsqlTimeSpan ReadPsv(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + { + var ticks = buf.ReadInt64(); + var day = buf.ReadInt32(); + var month = buf.ReadInt32(); + return new NpgsqlTimeSpan(month, day, ticks * 10); + } + + NpgsqlInterval INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + { + var ticks = buf.ReadInt64(); + var day = buf.ReadInt32(); + var month = buf.ReadInt32(); + return new NpgsqlInterval(month, day, ticks); + } + + /// + public override int ValidateAndGetLength(TimeSpan value, NpgsqlParameter? parameter) => 16; + + /// + public override int ValidateAndGetLength(NpgsqlTimeSpan value, NpgsqlParameter? parameter) => 16; + + /// + public int ValidateAndGetLength(NpgsqlInterval value, NpgsqlParameter? parameter) => 16; + + /// + public override void Write(NpgsqlTimeSpan value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + { + buf.WriteInt64(value.Ticks / 10); // TODO: round? + buf.WriteInt32(value.Days); + buf.WriteInt32(value.Months); + } + + // TODO: Can write directly from TimeSpan + /// + public override void Write(TimeSpan value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + => Write(value, buf, parameter); + + public void Write(NpgsqlInterval value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + { + buf.WriteInt64(value.Time); + buf.WriteInt32(value.Days); + buf.WriteInt32(value.Months); + } +} + +partial class IntervalHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + TimeSpan converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + NpgsqlTimeSpan converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + NpgsqlInterval converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type IntervalHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + TimeSpan converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + NpgsqlTimeSpan converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + NpgsqlInterval converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type IntervalHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/DateTimeHandlers/TimeHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/DateTimeHandlers/TimeHandler.cs new file mode 100644 index 0000000..1dcfbf1 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/DateTimeHandlers/TimeHandler.cs @@ -0,0 +1,77 @@ +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers.DateTimeHandlers; + +/// +/// A type handler for the PostgreSQL time data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-datetime.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class TimeHandler : NpgsqlSimpleTypeHandler +#if NET6_0_OR_GREATER + , INpgsqlSimpleTypeHandler +#endif +{ + /// + /// Constructs a . + /// + public TimeHandler(PostgresType postgresType) : base(postgresType) { } + + // PostgreSQL time resolution == 1 microsecond == 10 ticks + /// + public override TimeSpan Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => new(buf.ReadInt64() * 10); + + /// + public override int ValidateAndGetLength(TimeSpan value, NpgsqlParameter? parameter) => 8; + + /// + public override void Write(TimeSpan value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + => buf.WriteInt64(value.Ticks / 10); + +#if NET6_0_OR_GREATER + TimeOnly INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => new(buf.ReadInt64() * 10); + + public int ValidateAndGetLength(TimeOnly value, NpgsqlParameter? parameter) => 8; + + public void Write(TimeOnly value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + => buf.WriteInt64(value.Ticks / 10); +#endif +} + +partial class TimeHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + TimeSpan converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + TimeOnly converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type TimeHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + TimeSpan converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + TimeOnly converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type TimeHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/DateTimeHandlers/TimeTzHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/DateTimeHandlers/TimeTzHandler.cs new file mode 100644 index 0000000..8a595f7 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/DateTimeHandlers/TimeTzHandler.cs @@ -0,0 +1,74 @@ +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers.DateTimeHandlers; + +/// +/// A type handler for the PostgreSQL timetz data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-datetime.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class TimeTzHandler : NpgsqlSimpleTypeHandler +{ + // Binary Format: int64 expressing microseconds, int32 expressing timezone in seconds, negative + + /// + /// Constructs an . + /// + public TimeTzHandler(PostgresType postgresType) : base(postgresType) { } + + #region Read + + /// + public override DateTimeOffset Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + { + // Adjust from 1 microsecond to 100ns. Time zone (in seconds) is inverted. + var ticks = buf.ReadInt64() * 10; + var offset = new TimeSpan(0, 0, -buf.ReadInt32()); + return new DateTimeOffset(ticks + TimeSpan.TicksPerDay, offset); + } + + #endregion Read + + #region Write + + /// + public override int ValidateAndGetLength(DateTimeOffset value, NpgsqlParameter? parameter) => 12; + + /// + public override void Write(DateTimeOffset value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + { + buf.WriteInt64(value.TimeOfDay.Ticks / 10); + buf.WriteInt32(-(int)(value.Offset.Ticks / TimeSpan.TicksPerSecond)); + } + + #endregion Write + + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + DateTimeOffset converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type TimeTzHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + DateTimeOffset converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type TimeTzHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/DateTimeHandlers/TimestampHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/DateTimeHandlers/TimestampHandler.cs new file mode 100644 index 0000000..f2bfea5 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/DateTimeHandlers/TimestampHandler.cs @@ -0,0 +1,154 @@ +using System; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using NpgsqlTypes; +using static Npgsql.Util.Statics; +using static Npgsql.Internal.TypeHandlers.DateTimeHandlers.DateTimeUtils; +using System.Runtime.CompilerServices; + +#pragma warning disable 618 // NpgsqlDateTime is obsolete, remove in 7.0 + +namespace Npgsql.Internal.TypeHandlers.DateTimeHandlers; + +/// +/// A type handler for the PostgreSQL timestamp data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-datetime.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class TimestampHandler : NpgsqlSimpleTypeHandlerWithPsv, INpgsqlSimpleTypeHandler +{ + /// + /// Constructs a . + /// + public TimestampHandler(PostgresType postgresType) : base(postgresType) {} + + #region Read + + /// + public override DateTime Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => ReadDateTime(buf, DateTimeKind.Unspecified); + + /// + protected override NpgsqlDateTime ReadPsv(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => ReadNpgsqlDateTime(buf, len, fieldDescription); + + long INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => buf.ReadInt64(); + + #endregion Read + + #region Write + + /// + public override int ValidateAndGetLength(DateTime value, NpgsqlParameter? parameter) + => value.Kind != DateTimeKind.Utc || LegacyTimestampBehavior + ? 8 + : throw new InvalidCastException( + "Cannot write DateTime with Kind=UTC to PostgreSQL type 'timestamp without time zone', " + + "consider using 'timestamp with time zone'. " + + "Note that it's not possible to mix DateTimes with different Kinds in an array/range. " + + "See the Npgsql.EnableLegacyTimestampBehavior AppContext switch to enable legacy behavior."); + + /// + public override int ValidateAndGetLength(NpgsqlDateTime value, NpgsqlParameter? parameter) + => value.Kind != DateTimeKind.Utc || LegacyTimestampBehavior + ? 8 + : throw new InvalidCastException( + "Cannot write DateTime with Kind=UTC to PostgreSQL type 'timestamp without time zone', " + + "consider using 'timestamp with time zone'. " + + "Note that it's not possible to mix DateTimes with different Kinds in an array/range. " + + "See the Npgsql.EnableLegacyTimestampBehavior AppContext switch to enable legacy behavior."); + + /// + public int ValidateAndGetLength(long value, NpgsqlParameter? parameter) => 8; + + /// + public override void Write(DateTime value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + => WriteTimestamp(value, buf); + + /// + public override void Write(NpgsqlDateTime value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + => WriteTimestamp(value, buf); + + /// + public void Write(long value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + => buf.WriteInt64(value); + + #endregion Write + + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + { + int result; + if (value is DateTime) + { + DateTime value2 = (DateTime)value; + result = ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(value2, parameter); + } + else if (value is NpgsqlDateTime) + { + NpgsqlDateTime value3 = (NpgsqlDateTime)value; + result = ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(value3, parameter); + } + else + { + if (!(value is long)) + { + DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(56, 1); + defaultInterpolatedStringHandler.AppendLiteral("Can't write CLR type "); + defaultInterpolatedStringHandler.AppendFormatted(value.GetType()); + defaultInterpolatedStringHandler.AppendLiteral(" with handler type TimestampHandler"); + throw new InvalidCastException(defaultInterpolatedStringHandler.ToStringAndClear()); + } + + long value4 = (long)value; + result = ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(value4, parameter); + } + + return result; + } + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default(CancellationToken)) + { + Task result; + if (value is DateTime) + { + DateTime value2 = (DateTime)value; + result = WriteWithLength(value2, buf, lengthCache, parameter, async, cancellationToken); + } + else if (value is NpgsqlDateTime) + { + NpgsqlDateTime value3 = (NpgsqlDateTime)value; + result = WriteWithLength(value3, buf, lengthCache, parameter, async, cancellationToken); + } + else if (value is long) + { + long value4 = (long)value; + result = WriteWithLength(value4, buf, lengthCache, parameter, async, cancellationToken); + } + else if (!(value is DBNull)) + { + if (value != null) + { + DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(56, 1); + defaultInterpolatedStringHandler.AppendLiteral("Can't write CLR type "); + defaultInterpolatedStringHandler.AppendFormatted(value!.GetType()); + defaultInterpolatedStringHandler.AppendLiteral(" with handler type TimestampHandler"); + throw new InvalidCastException(defaultInterpolatedStringHandler.ToStringAndClear()); + } + + result = WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken); + } + else + { + result = WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken); + } + + return result; + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/DateTimeHandlers/TimestampTzHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/DateTimeHandlers/TimestampTzHandler.cs new file mode 100644 index 0000000..1f28489 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/DateTimeHandlers/TimestampTzHandler.cs @@ -0,0 +1,272 @@ +using System; +using System.Diagnostics; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using NpgsqlTypes; +using static Npgsql.Util.Statics; +using static Npgsql.Internal.TypeHandlers.DateTimeHandlers.DateTimeUtils; +using System.Runtime.CompilerServices; + +#pragma warning disable 618 // NpgsqlDateTime is obsolete, remove in 7.0 + +namespace Npgsql.Internal.TypeHandlers.DateTimeHandlers; + +/// +/// A type handler for the PostgreSQL timestamptz data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-datetime.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class TimestampTzHandler : NpgsqlSimpleTypeHandlerWithPsv, + INpgsqlSimpleTypeHandler, INpgsqlSimpleTypeHandler +{ + /// + /// Constructs an . + /// + public TimestampTzHandler(PostgresType postgresType) : base(postgresType) {} + + /// + public override NpgsqlTypeHandler CreateRangeHandler(PostgresType pgRangeType) + => new RangeHandler(pgRangeType, this); + + #region Read + + /// + public override DateTime Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + { + var dateTime = ReadDateTime(buf, DateTimeKind.Utc); + return LegacyTimestampBehavior && (DisableDateTimeInfinityConversions || dateTime != DateTime.MaxValue && dateTime != DateTime.MinValue) + ? dateTime.ToLocalTime() + : dateTime; + } + + /// + protected override NpgsqlDateTime ReadPsv(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + { + var ts = ReadNpgsqlDateTime(buf, len, fieldDescription); + + if (!ts.IsFinite) + return ts; + + var npgsqlDateTime = new NpgsqlDateTime(ts.Date, ts.Time, DateTimeKind.Utc); + return LegacyTimestampBehavior ? npgsqlDateTime.ToLocalTime() : npgsqlDateTime; + } + + DateTimeOffset INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + { + try + { + var value = buf.ReadInt64(); + switch (value) + { + case long.MaxValue: + return DisableDateTimeInfinityConversions + ? throw new InvalidCastException(NpgsqlStrings.CannotReadInfinityValue) + : DateTimeOffset.MaxValue; + case long.MinValue: + return DisableDateTimeInfinityConversions + ? throw new InvalidCastException(NpgsqlStrings.CannotReadInfinityValue) + : DateTimeOffset.MinValue; + default: + var dateTime = DecodeTimestamp(value, DateTimeKind.Utc); + return LegacyTimestampBehavior ? dateTime.ToLocalTime() : dateTime; + } + } + catch (ArgumentOutOfRangeException e) + { + throw new InvalidCastException("Out of the range of DateTime (year must be between 1 and 9999)", e); + } + } + + long INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => buf.ReadInt64(); + + #endregion Read + + #region Write + + /// + public override int ValidateAndGetLength(DateTime value, NpgsqlParameter? parameter) + => value.Kind == DateTimeKind.Utc || + value == DateTime.MinValue || // Allowed since this is default(DateTime) - sent without any timezone conversion. + value == DateTime.MaxValue && !DisableDateTimeInfinityConversions || + LegacyTimestampBehavior + ? 8 + : throw new InvalidCastException( + $"Cannot write DateTime with Kind={value.Kind} to PostgreSQL type 'timestamp with time zone', only UTC is supported. " + + "Note that it's not possible to mix DateTimes with different Kinds in an array/range. " + + "See the Npgsql.EnableLegacyTimestampBehavior AppContext switch to enable legacy behavior."); + + /// + public override int ValidateAndGetLength(NpgsqlDateTime value, NpgsqlParameter? parameter) + => value.Kind == DateTimeKind.Utc || + value == NpgsqlDateTime.Infinity || + value == NpgsqlDateTime.NegativeInfinity || + LegacyTimestampBehavior + ? 8 + : throw new InvalidCastException( + $"Cannot write DateTime with Kind={value.Kind} to PostgreSQL type 'timestamp with time zone', only UTC is supported. " + + "Note that it's not possible to mix DateTimes with different Kinds in an array/range. " + + "See the Npgsql.EnableLegacyTimestampBehavior AppContext switch to enable legacy behavior."); + + /// + public int ValidateAndGetLength(DateTimeOffset value, NpgsqlParameter? parameter) + => value.Offset == TimeSpan.Zero || LegacyTimestampBehavior + ? 8 + : throw new InvalidCastException( + $"Cannot write DateTimeOffset with Offset={value.Offset} to PostgreSQL type 'timestamp with time zone', " + + "only offset 0 (UTC) is supported. " + + "Note that it's not possible to mix DateTimes with different Kinds in an array/range. " + + "See the Npgsql.EnableLegacyTimestampBehavior AppContext switch to enable legacy behavior."); + + /// + public int ValidateAndGetLength(long value, NpgsqlParameter? parameter) => 8; + + /// + public override void Write(DateTime value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + { + if (LegacyTimestampBehavior) + { + switch (value.Kind) + { + case DateTimeKind.Unspecified: + case DateTimeKind.Utc: + break; + case DateTimeKind.Local: + value = value.ToUniversalTime(); + break; + default: + throw new InvalidOperationException($"Internal Npgsql bug: unexpected value {value.Kind} of enum {nameof(DateTimeKind)}. Please file a bug."); + } + } + else + Debug.Assert(value.Kind == DateTimeKind.Utc || value == DateTime.MinValue || value == DateTime.MaxValue); + + WriteTimestamp(value, buf); + } + + /// + public override void Write(NpgsqlDateTime value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + { + if (LegacyTimestampBehavior) + { + switch (value.Kind) + { + case DateTimeKind.Unspecified: + case DateTimeKind.Utc: + break; + case DateTimeKind.Local: + value = value.ToUniversalTime(); + break; + default: + throw new InvalidOperationException($"Internal Npgsql bug: unexpected value {value.Kind} of enum {nameof(DateTimeKind)}. Please file a bug."); + } + } + else + Debug.Assert(value.Kind == DateTimeKind.Utc || !value.IsFinite); + + WriteTimestamp(value, buf); + } + + /// + public void Write(DateTimeOffset value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + { + if (LegacyTimestampBehavior) + value = value.ToUniversalTime(); + + Debug.Assert(value.Offset == TimeSpan.Zero); + + WriteTimestamp(value.DateTime, buf); + } + + /// + public void Write(long value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + => buf.WriteInt64(value); + + #endregion Write + + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + { + int result; + if (value is DateTime) + { + DateTime value2 = (DateTime)value; + result = ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(value2, parameter); + } + else if (value is NpgsqlDateTime) + { + NpgsqlDateTime value3 = (NpgsqlDateTime)value; + result = ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(value3, parameter); + } + else if (value is DateTimeOffset) + { + DateTimeOffset value4 = (DateTimeOffset)value; + result = ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(value4, parameter); + } + else + { + if (!(value is long)) + { + DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(58, 1); + defaultInterpolatedStringHandler.AppendLiteral("Can't write CLR type "); + defaultInterpolatedStringHandler.AppendFormatted(value.GetType()); + defaultInterpolatedStringHandler.AppendLiteral(" with handler type TimestampTzHandler"); + throw new InvalidCastException(defaultInterpolatedStringHandler.ToStringAndClear()); + } + + long value5 = (long)value; + result = ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(value5, parameter); + } + + return result; + } + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default(CancellationToken)) + { + Task result; + if (value is DateTime) + { + DateTime value2 = (DateTime)value; + result = WriteWithLength(value2, buf, lengthCache, parameter, async, cancellationToken); + } + else if (value is NpgsqlDateTime) + { + NpgsqlDateTime value3 = (NpgsqlDateTime)value; + result = WriteWithLength(value3, buf, lengthCache, parameter, async, cancellationToken); + } + else if (value is DateTimeOffset) + { + DateTimeOffset value4 = (DateTimeOffset)value; + result = WriteWithLength(value4, buf, lengthCache, parameter, async, cancellationToken); + } + else if (value is long) + { + long value5 = (long)value; + result = WriteWithLength(value5, buf, lengthCache, parameter, async, cancellationToken); + } + else if (!(value is DBNull)) + { + if (value != null) + { + DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(58, 1); + defaultInterpolatedStringHandler.AppendLiteral("Can't write CLR type "); + defaultInterpolatedStringHandler.AppendFormatted(value!.GetType()); + defaultInterpolatedStringHandler.AppendLiteral(" with handler type TimestampTzHandler"); + throw new InvalidCastException(defaultInterpolatedStringHandler.ToStringAndClear()); + } + + result = WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken); + } + else + { + result = WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken); + } + + return result; + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/EnumHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/EnumHandler.cs new file mode 100644 index 0000000..9d0b198 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/EnumHandler.cs @@ -0,0 +1,92 @@ +using System.Diagnostics; +using System.Reflection; +using System.Text; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers; + +/// +/// Interface implemented by all concrete handlers which handle enums +/// +interface IEnumHandler +{ + /// + /// The CLR enum type mapped to the PostgreSQL enum + /// + Type EnumType { get; } +} + +partial class EnumHandler : NpgsqlSimpleTypeHandler, IEnumHandler where TEnum : struct, Enum +{ + readonly Dictionary _enumToLabel; + readonly Dictionary _labelToEnum; + + public Type EnumType => typeof(TEnum); + + #region Construction + + internal EnumHandler(PostgresEnumType postgresType, Dictionary enumToLabel, Dictionary labelToEnum) + : base(postgresType) + { + Debug.Assert(typeof(TEnum).GetTypeInfo().IsEnum, "EnumHandler instantiated for non-enum type"); + _enumToLabel = enumToLabel; + _labelToEnum = labelToEnum; + } + + #endregion + + #region Read + + public override TEnum Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + { + var str = buf.ReadString(len); + var success = _labelToEnum.TryGetValue(str, out var value); + + if (!success) + throw new InvalidCastException($"Received enum value '{str}' from database which wasn't found on enum {typeof(TEnum)}"); + + return value; + } + + #endregion + + #region Write + + public override int ValidateAndGetLength(TEnum value, NpgsqlParameter? parameter) + => _enumToLabel.TryGetValue(value, out var str) + ? Encoding.UTF8.GetByteCount(str) + : throw new InvalidCastException($"Can't write value {value} as enum {typeof(TEnum)}"); + + public override void Write(TEnum value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + { + if (!_enumToLabel.TryGetValue(value, out var str)) + throw new InvalidCastException($"Can't write value {value} as enum {typeof(TEnum)}"); + buf.WriteString(str); + } + + #endregion + + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + TEnum converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type EnumHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + TEnum converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type EnumHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/FullTextSearchHandlers/TsQueryHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/FullTextSearchHandlers/TsQueryHandler.cs new file mode 100644 index 0000000..6482651 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/FullTextSearchHandlers/TsQueryHandler.cs @@ -0,0 +1,332 @@ +using System.Text; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using NpgsqlTypes; + +// TODO: Need to work on the nullability here +#nullable disable +#pragma warning disable CS8632 + +namespace Npgsql.Internal.TypeHandlers.FullTextSearchHandlers; + +/// +/// A type handler for the PostgreSQL tsquery data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-textsearch.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class TsQueryHandler : NpgsqlTypeHandler, + INpgsqlTypeHandler, INpgsqlTypeHandler, + INpgsqlTypeHandler, INpgsqlTypeHandler, + INpgsqlTypeHandler, INpgsqlTypeHandler +{ + // 1 (type) + 1 (weight) + 1 (is prefix search) + 2046 (max str len) + 1 (null terminator) + const int MaxSingleTokenBytes = 2050; + + readonly Stack _stack = new(); + + public TsQueryHandler(PostgresType pgType) : base(pgType) { } + + #region Read + + /// + public override async ValueTask Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + { + await buf.Ensure(4, async); + var numTokens = buf.ReadInt32(); + if (numTokens == 0) + return new NpgsqlTsQueryEmpty(); + + NpgsqlTsQuery? value = null; + var nodes = new Stack>(); + len -= 4; + + for (var tokenPos = 0; tokenPos < numTokens; tokenPos++) + { + await buf.Ensure(Math.Min(len, MaxSingleTokenBytes), async); + var readPos = buf.ReadPosition; + + var isOper = buf.ReadByte() == 2; + if (isOper) + { + var operKind = (NpgsqlTsQuery.NodeKind)buf.ReadByte(); + if (operKind == NpgsqlTsQuery.NodeKind.Not) + { + var node = new NpgsqlTsQueryNot(null); + InsertInTree(node, nodes, ref value); + nodes.Push(new Tuple(node, 0)); + } + else + { + var node = operKind switch + { + NpgsqlTsQuery.NodeKind.And => (NpgsqlTsQuery)new NpgsqlTsQueryAnd(null, null), + NpgsqlTsQuery.NodeKind.Or => new NpgsqlTsQueryOr(null, null), + NpgsqlTsQuery.NodeKind.Phrase => new NpgsqlTsQueryFollowedBy(null, buf.ReadInt16(), null), + _ => throw new InvalidOperationException($"Internal Npgsql bug: unexpected value {operKind} of enum {nameof(NpgsqlTsQuery.NodeKind)}. Please file a bug.") + }; + + InsertInTree(node, nodes, ref value); + + nodes.Push(new Tuple(node, 1)); + nodes.Push(new Tuple(node, 2)); + } + } + else + { + var weight = (NpgsqlTsQueryLexeme.Weight)buf.ReadByte(); + var prefix = buf.ReadByte() != 0; + var str = buf.ReadNullTerminatedString(); + InsertInTree(new NpgsqlTsQueryLexeme(str, weight, prefix), nodes, ref value); + } + + len -= buf.ReadPosition - readPos; + } + + if (nodes.Count != 0) + throw new InvalidOperationException("Internal Npgsql bug, please report."); + + return value!; + + static void InsertInTree(NpgsqlTsQuery node, Stack> nodes, ref NpgsqlTsQuery? value) + { + if (nodes.Count == 0) + value = node; + else + { + var parent = nodes.Pop(); + if (parent.Item2 == 0) + ((NpgsqlTsQueryNot)parent.Item1).Child = node; + else if (parent.Item2 == 1) + ((NpgsqlTsQueryBinOp)parent.Item1).Left = node; + else + ((NpgsqlTsQueryBinOp)parent.Item1).Right = node; + } + } + } + + async ValueTask INpgsqlTypeHandler.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + => (NpgsqlTsQueryEmpty)await Read(buf, len, async, fieldDescription); + + async ValueTask INpgsqlTypeHandler.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + => (NpgsqlTsQueryLexeme)await Read(buf, len, async, fieldDescription); + + async ValueTask INpgsqlTypeHandler.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + => (NpgsqlTsQueryNot)await Read(buf, len, async, fieldDescription); + + async ValueTask INpgsqlTypeHandler.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + => (NpgsqlTsQueryAnd)await Read(buf, len, async, fieldDescription); + + async ValueTask INpgsqlTypeHandler.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + => (NpgsqlTsQueryOr)await Read(buf, len, async, fieldDescription); + + async ValueTask INpgsqlTypeHandler.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + => (NpgsqlTsQueryFollowedBy)await Read(buf, len, async, fieldDescription); + + #endregion Read + + #region Write + + /// + public override int ValidateAndGetLength(NpgsqlTsQuery value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value.Kind == NpgsqlTsQuery.NodeKind.Empty + ? 4 + : 4 + GetNodeLength(value); + + int GetNodeLength(NpgsqlTsQuery node) + { + // TODO: Figure out the nullability strategy here + switch (node.Kind) + { + case NpgsqlTsQuery.NodeKind.Lexeme: + var strLen = Encoding.UTF8.GetByteCount(((NpgsqlTsQueryLexeme)node).Text); + if (strLen > 2046) + throw new InvalidCastException("Lexeme text too long. Must be at most 2046 bytes in UTF8."); + return 4 + strLen; + case NpgsqlTsQuery.NodeKind.And: + case NpgsqlTsQuery.NodeKind.Or: + return 2 + GetNodeLength(((NpgsqlTsQueryBinOp)node).Left) + GetNodeLength(((NpgsqlTsQueryBinOp)node).Right); + case NpgsqlTsQuery.NodeKind.Phrase: + // 2 additional bytes for uint16 phrase operator "distance" field. + return 4 + GetNodeLength(((NpgsqlTsQueryBinOp)node).Left) + GetNodeLength(((NpgsqlTsQueryBinOp)node).Right); + case NpgsqlTsQuery.NodeKind.Not: + return 2 + GetNodeLength(((NpgsqlTsQueryNot)node).Child); + case NpgsqlTsQuery.NodeKind.Empty: + throw new InvalidOperationException("Empty tsquery nodes must be top-level"); + default: + throw new InvalidOperationException("Illegal node kind: " + node.Kind); + } + } + + /// + public override async Task Write(NpgsqlTsQuery query, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + var numTokens = GetTokenCount(query); + + if (buf.WriteSpaceLeft < 4) + await buf.Flush(async, cancellationToken); + buf.WriteInt32(numTokens); + + if (numTokens == 0) + return; + + _stack.Push(query); + + while (_stack.Count > 0) + { + if (buf.WriteSpaceLeft < 2) + await buf.Flush(async, cancellationToken); + + if (_stack.Peek().Kind == NpgsqlTsQuery.NodeKind.Lexeme && buf.WriteSpaceLeft < MaxSingleTokenBytes) + await buf.Flush(async, cancellationToken); + + var node = _stack.Pop(); + buf.WriteByte(node.Kind == NpgsqlTsQuery.NodeKind.Lexeme ? (byte)1 : (byte)2); + if (node.Kind != NpgsqlTsQuery.NodeKind.Lexeme) + { + buf.WriteByte((byte)node.Kind); + if (node.Kind == NpgsqlTsQuery.NodeKind.Not) + _stack.Push(((NpgsqlTsQueryNot)node).Child); + else + { + if (node.Kind == NpgsqlTsQuery.NodeKind.Phrase) + buf.WriteInt16(((NpgsqlTsQueryFollowedBy)node).Distance); + + _stack.Push(((NpgsqlTsQueryBinOp)node).Left); + _stack.Push(((NpgsqlTsQueryBinOp)node).Right); + } + } + else + { + var lexemeNode = (NpgsqlTsQueryLexeme)node; + buf.WriteByte((byte)lexemeNode.Weights); + buf.WriteByte(lexemeNode.IsPrefixSearch ? (byte)1 : (byte)0); + buf.WriteString(lexemeNode.Text); + buf.WriteByte(0); + } + } + + _stack.Clear(); + } + + int GetTokenCount(NpgsqlTsQuery node) + { + switch (node.Kind) + { + case NpgsqlTsQuery.NodeKind.Lexeme: + return 1; + case NpgsqlTsQuery.NodeKind.And: + case NpgsqlTsQuery.NodeKind.Or: + case NpgsqlTsQuery.NodeKind.Phrase: + return 1 + GetTokenCount(((NpgsqlTsQueryBinOp)node).Left) + GetTokenCount(((NpgsqlTsQueryBinOp)node).Right); + case NpgsqlTsQuery.NodeKind.Not: + return 1 + GetTokenCount(((NpgsqlTsQueryNot)node).Child); + case NpgsqlTsQuery.NodeKind.Empty: + return 0; + } + return -1; + } + + /// + public int ValidateAndGetLength(NpgsqlTsQueryOr value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLength((NpgsqlTsQuery)value, ref lengthCache, parameter); + + /// + public int ValidateAndGetLength(NpgsqlTsQueryAnd value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLength((NpgsqlTsQuery)value, ref lengthCache, parameter); + + /// + public int ValidateAndGetLength(NpgsqlTsQueryNot value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLength((NpgsqlTsQuery)value, ref lengthCache, parameter); + + /// + public int ValidateAndGetLength(NpgsqlTsQueryLexeme value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLength((NpgsqlTsQuery)value, ref lengthCache, parameter); + + /// + public int ValidateAndGetLength(NpgsqlTsQueryEmpty value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLength((NpgsqlTsQuery)value, ref lengthCache, parameter); + + /// + public int ValidateAndGetLength(NpgsqlTsQueryFollowedBy value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLength((NpgsqlTsQuery)value, ref lengthCache, parameter); + + /// + public Task Write(NpgsqlTsQueryOr value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => Write((NpgsqlTsQuery)value, buf, lengthCache, parameter, async, cancellationToken); + + /// + public Task Write(NpgsqlTsQueryAnd value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => Write((NpgsqlTsQuery)value, buf, lengthCache, parameter, async, cancellationToken); + + /// + public Task Write(NpgsqlTsQueryNot value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => Write((NpgsqlTsQuery)value, buf, lengthCache, parameter, async, cancellationToken); + + /// + public Task Write(NpgsqlTsQueryLexeme value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => Write((NpgsqlTsQuery)value, buf, lengthCache, parameter, async, cancellationToken); + + /// + public Task Write(NpgsqlTsQueryEmpty value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => Write((NpgsqlTsQuery)value, buf, lengthCache, parameter, async, cancellationToken); + + /// + public Task Write( + NpgsqlTsQueryFollowedBy value, + NpgsqlWriteBuffer buf, + NpgsqlLengthCache? lengthCache, + NpgsqlParameter? parameter, + bool async, + CancellationToken cancellationToken = default) + => Write((NpgsqlTsQuery)value, buf, lengthCache, parameter, async, cancellationToken); + + #endregion Write + + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + NpgsqlTsQueryEmpty converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + NpgsqlTsQueryLexeme converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + NpgsqlTsQueryNot converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + NpgsqlTsQueryAnd converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + NpgsqlTsQueryOr converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + NpgsqlTsQueryFollowedBy converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type TsQueryHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + NpgsqlTsQueryEmpty converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + NpgsqlTsQueryLexeme converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + NpgsqlTsQueryNot converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + NpgsqlTsQueryAnd converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + NpgsqlTsQueryOr converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + NpgsqlTsQueryFollowedBy converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type TsQueryHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/FullTextSearchHandlers/TsVectorHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/FullTextSearchHandlers/TsVectorHandler.cs new file mode 100644 index 0000000..2755f38 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/FullTextSearchHandlers/TsVectorHandler.cs @@ -0,0 +1,114 @@ +using System.Text; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using NpgsqlTypes; + +namespace Npgsql.Internal.TypeHandlers.FullTextSearchHandlers; + +/// +/// A type handler for the PostgreSQL tsvector data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-textsearch.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class TsVectorHandler : NpgsqlTypeHandler +{ + // 2561 = 2046 (max length lexeme string) + (1) null terminator + + // 2 (num_pos) + sizeof(int16) * 256 (max_num_pos (positions/wegihts)) + const int MaxSingleLexemeBytes = 2561; + + public TsVectorHandler(PostgresType pgType) : base(pgType) { } + + #region Read + + /// + public override async ValueTask Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + { + await buf.Ensure(4, async); + var numLexemes = buf.ReadInt32(); + len -= 4; + + var lexemes = new List(); + for (var lexemePos = 0; lexemePos < numLexemes; lexemePos++) + { + await buf.Ensure(Math.Min(len, MaxSingleLexemeBytes), async); + var posBefore = buf.ReadPosition; + + List? positions = null; + + var lexemeString = buf.ReadNullTerminatedString(); + int numPositions = buf.ReadInt16(); + for (var i = 0; i < numPositions; i++) + { + var wordEntryPos = buf.ReadInt16(); + if (positions == null) + positions = new List(); + positions.Add(new NpgsqlTsVector.Lexeme.WordEntryPos(wordEntryPos)); + } + + lexemes.Add(new NpgsqlTsVector.Lexeme(lexemeString, positions, true)); + + len -= buf.ReadPosition - posBefore; + } + + return new NpgsqlTsVector(lexemes, true); + } + + #endregion Read + + #region Write + + // TODO: Implement length cache + /// + public override int ValidateAndGetLength(NpgsqlTsVector value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => 4 + value.Sum(l => Encoding.UTF8.GetByteCount(l.Text) + 1 + 2 + l.Count * 2); + + /// + public override async Task Write(NpgsqlTsVector vector, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + if (buf.WriteSpaceLeft < 4) + await buf.Flush(async, cancellationToken); + buf.WriteInt32(vector.Count); + + foreach (var lexeme in vector) + { + if (buf.WriteSpaceLeft < MaxSingleLexemeBytes) + await buf.Flush(async, cancellationToken); + + buf.WriteString(lexeme.Text); + buf.WriteByte(0); + buf.WriteInt16(lexeme.Count); + for (var i = 0; i < lexeme.Count; i++) + buf.WriteInt16(lexeme[i].Value); + } + } + + #endregion Write + + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + NpgsqlTsVector converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type TsVectorHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + NpgsqlTsVector converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type TsVectorHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/GeometricHandlers/BoxHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/GeometricHandlers/BoxHandler.cs new file mode 100644 index 0000000..04741c3 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/GeometricHandlers/BoxHandler.cs @@ -0,0 +1,63 @@ +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using NpgsqlTypes; + +namespace Npgsql.Internal.TypeHandlers.GeometricHandlers; + +/// +/// A type handler for the PostgreSQL box data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-geometric.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class BoxHandler : NpgsqlSimpleTypeHandler +{ + public BoxHandler(PostgresType pgType) : base(pgType) {} + + /// + public override NpgsqlBox Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => new( + new NpgsqlPoint(buf.ReadDouble(), buf.ReadDouble()), + new NpgsqlPoint(buf.ReadDouble(), buf.ReadDouble()) + ); + + /// + public override int ValidateAndGetLength(NpgsqlBox value, NpgsqlParameter? parameter) + => 32; + + /// + public override void Write(NpgsqlBox value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + { + buf.WriteDouble(value.Right); + buf.WriteDouble(value.Top); + buf.WriteDouble(value.Left); + buf.WriteDouble(value.Bottom); + } + + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + NpgsqlBox converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type BoxHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + NpgsqlBox converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type BoxHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/GeometricHandlers/CircleHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/GeometricHandlers/CircleHandler.cs new file mode 100644 index 0000000..cc761a1 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/GeometricHandlers/CircleHandler.cs @@ -0,0 +1,59 @@ +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using NpgsqlTypes; + +namespace Npgsql.Internal.TypeHandlers.GeometricHandlers; + +/// +/// A type handler for the PostgreSQL circle data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-geometric.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class CircleHandler : NpgsqlSimpleTypeHandler +{ + public CircleHandler(PostgresType pgType) : base(pgType) {} + + /// + public override NpgsqlCircle Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => new(buf.ReadDouble(), buf.ReadDouble(), buf.ReadDouble()); + + /// + public override int ValidateAndGetLength(NpgsqlCircle value, NpgsqlParameter? parameter) + => 24; + + /// + public override void Write(NpgsqlCircle value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + { + buf.WriteDouble(value.X); + buf.WriteDouble(value.Y); + buf.WriteDouble(value.Radius); + } + + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + NpgsqlCircle converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type CircleHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + NpgsqlCircle converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type CircleHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/GeometricHandlers/LineHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/GeometricHandlers/LineHandler.cs new file mode 100644 index 0000000..436a1a8 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/GeometricHandlers/LineHandler.cs @@ -0,0 +1,62 @@ +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using NpgsqlTypes; + +namespace Npgsql.Internal.TypeHandlers.GeometricHandlers; + +/// +/// A type handler for the PostgreSQL line data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-geometric.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class LineHandler : NpgsqlSimpleTypeHandler +{ + public LineHandler(PostgresType pgType) : base(pgType) {} + + /// + public override NpgsqlLine Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => new(buf.ReadDouble(), buf.ReadDouble(), buf.ReadDouble()); + + /// + public override int ValidateAndGetLength(NpgsqlLine value, NpgsqlParameter? parameter) + => 24; + + /// + public override void Write(NpgsqlLine value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + { + buf.WriteDouble(value.A); + buf.WriteDouble(value.B); + buf.WriteDouble(value.C); + } +} + +partial class LineHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + NpgsqlLine converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type LineHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + NpgsqlLine converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type LineHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/GeometricHandlers/LineSegmentHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/GeometricHandlers/LineSegmentHandler.cs new file mode 100644 index 0000000..7d52a99 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/GeometricHandlers/LineSegmentHandler.cs @@ -0,0 +1,63 @@ +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using NpgsqlTypes; + +namespace Npgsql.Internal.TypeHandlers.GeometricHandlers; + +/// +/// A type handler for the PostgreSQL lseg data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-geometric.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class LineSegmentHandler : NpgsqlSimpleTypeHandler +{ + public LineSegmentHandler(PostgresType pgType) : base(pgType) { } + + /// + public override NpgsqlLSeg Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => new(buf.ReadDouble(), buf.ReadDouble(), buf.ReadDouble(), buf.ReadDouble()); + + /// + public override int ValidateAndGetLength(NpgsqlLSeg value, NpgsqlParameter? parameter) + => 32; + + /// + public override void Write(NpgsqlLSeg value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + { + buf.WriteDouble(value.Start.X); + buf.WriteDouble(value.Start.Y); + buf.WriteDouble(value.End.X); + buf.WriteDouble(value.End.Y); + } +} + +partial class LineSegmentHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + NpgsqlLSeg converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type LineSegmentHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + NpgsqlLSeg converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type LineSegmentHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/GeometricHandlers/PathHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/GeometricHandlers/PathHandler.cs new file mode 100644 index 0000000..026037f --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/GeometricHandlers/PathHandler.cs @@ -0,0 +1,96 @@ +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using NpgsqlTypes; + +namespace Npgsql.Internal.TypeHandlers.GeometricHandlers; + +/// +/// A type handler for the PostgreSQL path data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-geometric.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class PathHandler : NpgsqlTypeHandler +{ + public PathHandler(PostgresType pgType) : base(pgType) { } + + #region Read + + /// + public override async ValueTask Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + { + await buf.Ensure(5, async); + var open = buf.ReadByte() switch + { + 1 => false, + 0 => true, + _ => throw new Exception("Error decoding binary geometric path: bad open byte") + }; + + var numPoints = buf.ReadInt32(); + var result = new NpgsqlPath(numPoints, open); + for (var i = 0; i < numPoints; i++) + { + await buf.Ensure(16, async); + result.Add(new NpgsqlPoint(buf.ReadDouble(), buf.ReadDouble())); + } + return result; + } + + #endregion + + #region Write + + /// + public override int ValidateAndGetLength(NpgsqlPath value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => 5 + value.Count * 16; + + /// + public override async Task Write(NpgsqlPath value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + if (buf.WriteSpaceLeft < 5) + await buf.Flush(async, cancellationToken); + buf.WriteByte((byte)(value.Open ? 0 : 1)); + buf.WriteInt32(value.Count); + + foreach (var p in value) + { + if (buf.WriteSpaceLeft < 16) + await buf.Flush(async, cancellationToken); + buf.WriteDouble(p.X); + buf.WriteDouble(p.Y); + } + } + + #endregion +} + +partial class PathHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + NpgsqlPath converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type PathHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + NpgsqlPath converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type PathHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/GeometricHandlers/PointHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/GeometricHandlers/PointHandler.cs new file mode 100644 index 0000000..710ee44 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/GeometricHandlers/PointHandler.cs @@ -0,0 +1,61 @@ +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using NpgsqlTypes; + +namespace Npgsql.Internal.TypeHandlers.GeometricHandlers; + +/// +/// A type handler for the PostgreSQL point data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-geometric.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class PointHandler : NpgsqlSimpleTypeHandler +{ + public PointHandler(PostgresType pgType) : base(pgType) {} + + /// + public override NpgsqlPoint Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => new(buf.ReadDouble(), buf.ReadDouble()); + + /// + public override int ValidateAndGetLength(NpgsqlPoint value, NpgsqlParameter? parameter) + => 16; + + /// + public override void Write(NpgsqlPoint value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + { + buf.WriteDouble(value.X); + buf.WriteDouble(value.Y); + } +} + +partial class PointHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + NpgsqlPoint converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type PointHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + NpgsqlPoint converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type PointHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/GeometricHandlers/PolygonHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/GeometricHandlers/PolygonHandler.cs new file mode 100644 index 0000000..9d67efa --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/GeometricHandlers/PolygonHandler.cs @@ -0,0 +1,90 @@ +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using NpgsqlTypes; + +namespace Npgsql.Internal.TypeHandlers.GeometricHandlers; + +/// +/// A type handler for the PostgreSQL polygon data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-geometric.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class PolygonHandler : NpgsqlTypeHandler +{ + public PolygonHandler(PostgresType pgType) : base(pgType) {} + + #region Read + + /// + public override async ValueTask Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + { + await buf.Ensure(4, async); + var numPoints = buf.ReadInt32(); + var result = new NpgsqlPolygon(numPoints); + for (var i = 0; i < numPoints; i++) + { + await buf.Ensure(16, async); + result.Add(new NpgsqlPoint(buf.ReadDouble(), buf.ReadDouble())); + } + return result; + } + + #endregion + + #region Write + + /// + public override int ValidateAndGetLength(NpgsqlPolygon value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => 4 + value.Count * 16; + + /// + public override async Task Write(NpgsqlPolygon value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + if (buf.WriteSpaceLeft < 4) + await buf.Flush(async, cancellationToken); + buf.WriteInt32(value.Count); + + foreach (var p in value) + { + if (buf.WriteSpaceLeft < 16) + await buf.Flush(async, cancellationToken); + buf.WriteDouble(p.X); + buf.WriteDouble(p.Y); + } + } + + #endregion +} + +partial class PolygonHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + NpgsqlPolygon converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type PolygonHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + NpgsqlPolygon converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type PolygonHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/HstoreHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/HstoreHandler.cs new file mode 100644 index 0000000..72bf63d --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/HstoreHandler.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +#if !NETSTANDARD2_0 && !NETSTANDARD2_1 +using System.Collections.Immutable; +#endif + +namespace Npgsql.Internal.TypeHandlers +{ + /// + /// A type handler for the PostgreSQL hstore extension data type, which stores sets of key/value pairs within a + /// single PostgreSQL value. + /// + /// + /// See https://www.postgresql.org/docs/current/hstore.html. + /// + /// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it + /// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. + /// Use it at your own risk. + /// +#pragma warning disable CA1061 // Do not hide base class methods + public class HstoreHandler : + NpgsqlTypeHandler>, + INpgsqlTypeHandler> +#if !NETSTANDARD2_0 && !NETSTANDARD2_1 + , INpgsqlTypeHandler> +#endif + { + /// + /// The text handler to which we delegate encoding/decoding of the actual strings + /// + readonly TextHandler _textHandler; + + internal HstoreHandler(PostgresType postgresType, TextHandler textHandler) + : base(postgresType) + => _textHandler = textHandler; + + #region Write + + /// + public int ValidateAndGetLength(IDictionary value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + { + lengthCache ??= new NpgsqlLengthCache(1); + if (lengthCache.IsPopulated) + return lengthCache.Get(); + + // Leave empty slot for the entire hstore length, and go ahead an populate the individual string slots + var pos = lengthCache.Position; + lengthCache.Set(0); + + var totalLen = 4; // Number of key-value pairs + foreach (var kv in value) + { + totalLen += 8; // Key length + value length + if (kv.Key == null) + throw new FormatException("HSTORE doesn't support null keys"); + totalLen += _textHandler.ValidateAndGetLength(kv.Key, ref lengthCache, null); + if (kv.Value != null) + totalLen += _textHandler.ValidateAndGetLength(kv.Value!, ref lengthCache, null); + } + + return lengthCache.Lengths[pos] = totalLen; + } + + /// + public override int ValidateAndGetLength(Dictionary value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLength(value, ref lengthCache, parameter); + + /// + public override int ValidateObjectAndGetLength(object? value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { +#if !NETSTANDARD2_0 && !NETSTANDARD2_1 + ImmutableDictionary converted => ((INpgsqlTypeHandler>)this).ValidateAndGetLength(converted, ref lengthCache, parameter), +#endif + Dictionary converted => ((INpgsqlTypeHandler>)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + IDictionary converted => ((INpgsqlTypeHandler>)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + DBNull => 0, + null => 0, + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type HstoreHandler") + }; + + /// + public override Task WriteObjectWithLength( + object? value, + NpgsqlWriteBuffer buf, + NpgsqlLengthCache? lengthCache, + NpgsqlParameter? parameter, + bool async, + CancellationToken cancellationToken = default) + => value switch + { +#if !NETSTANDARD2_0 && !NETSTANDARD2_1 + ImmutableDictionary converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), +#endif + Dictionary converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + IDictionary converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type BoolHandler") + }; + + /// + public async Task Write(IDictionary value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + if (buf.WriteSpaceLeft < 4) + await buf.Flush(async, cancellationToken); + buf.WriteInt32(value.Count); + if (value.Count == 0) + return; + + foreach (var kv in value) + { + await _textHandler.WriteWithLength(kv.Key, buf, lengthCache, parameter, async, cancellationToken); + await _textHandler.WriteWithLength(kv.Value, buf, lengthCache, parameter, async, cancellationToken); + } + } + + /// + public override Task Write(Dictionary value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => Write(value, buf, lengthCache, parameter, async, cancellationToken); + + #endregion + + #region Read + + async ValueTask ReadInto(T dictionary, int numElements, NpgsqlReadBuffer buf, bool async) + where T : IDictionary + { + for (var i = 0; i < numElements; i++) + { + await buf.Ensure(4, async); + var keyLen = buf.ReadInt32(); + Debug.Assert(keyLen != -1); + var key = await _textHandler.Read(buf, keyLen, async); + + await buf.Ensure(4, async); + var valueLen = buf.ReadInt32(); + + dictionary[key] = valueLen == -1 + ? null + : await _textHandler.Read(buf, valueLen, async); + } + return dictionary; + } + + /// + public override async ValueTask> Read(NpgsqlReadBuffer buf, int len, bool async, + FieldDescription? fieldDescription = null) + { + await buf.Ensure(4, async); + var numElements = buf.ReadInt32(); + return await ReadInto(new Dictionary(numElements), numElements, buf, async); + } + + ValueTask> INpgsqlTypeHandler>.Read( + NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + => new(Read(buf, len, async, fieldDescription).Result); + + #endregion + +#if !NETSTANDARD2_0 && !NETSTANDARD2_1 + #region ImmutableDictionary + + /// + public int ValidateAndGetLength( + ImmutableDictionary value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLength((IDictionary)value, ref lengthCache, parameter); + + /// + public Task Write(ImmutableDictionary value, + NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => Write((IDictionary)value, buf, lengthCache, parameter, async, cancellationToken); + + async ValueTask> INpgsqlTypeHandler>.Read( + NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + { + await buf.Ensure(4, async); + var numElements = buf.ReadInt32(); + return (await ReadInto(ImmutableDictionary.Empty.ToBuilder(), numElements, buf, async)) + .ToImmutable(); + } + + #endregion +#endif + } +#pragma warning restore CA1061 // Do not hide base class methods +} diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/InternalTypeHandlers/Int2VectorHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/InternalTypeHandlers/Int2VectorHandler.cs new file mode 100644 index 0000000..006c086 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/InternalTypeHandlers/Int2VectorHandler.cs @@ -0,0 +1,21 @@ +using System; +using Npgsql.Internal.TypeHandlers.NumericHandlers; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using Npgsql.TypeMapping; +using NpgsqlTypes; + +namespace Npgsql.Internal.TypeHandlers.InternalTypeHandlers; + +/// +/// An int2vector is simply a regular array of shorts, with the sole exception that its lower bound must +/// be 0 (we send 1 for regular arrays). +/// +class Int2VectorHandler : ArrayHandler +{ + public Int2VectorHandler(PostgresType arrayPostgresType, PostgresType postgresShortType) + : base(arrayPostgresType, new Int16Handler(postgresShortType), ArrayNullabilityMode.Never, 0) { } + + public override NpgsqlTypeHandler CreateArrayHandler(PostgresArrayType pgArrayType, ArrayNullabilityMode arrayNullabilityMode) + => new ArrayHandler>(pgArrayType, this, arrayNullabilityMode); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/InternalTypeHandlers/InternalCharHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/InternalTypeHandlers/InternalCharHandler.cs new file mode 100644 index 0000000..1d2726b --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/InternalTypeHandlers/InternalCharHandler.cs @@ -0,0 +1,128 @@ +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers.InternalTypeHandlers; + +/// +/// A type handler for the PostgreSQL "char" type, used only internally. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-character.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class InternalCharHandler : NpgsqlSimpleTypeHandler, + INpgsqlSimpleTypeHandler, INpgsqlSimpleTypeHandler, INpgsqlSimpleTypeHandler, INpgsqlSimpleTypeHandler +{ + public InternalCharHandler(PostgresType pgType) : base(pgType) {} + + #region Read + + /// + public override char Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => (char)buf.ReadByte(); + + byte INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => buf.ReadByte(); + + short INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => buf.ReadByte(); + + int INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => buf.ReadByte(); + + long INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => buf.ReadByte(); + + #endregion + + #region Write + + /// + public int ValidateAndGetLength(byte value, NpgsqlParameter? parameter) => 1; + + /// + public override int ValidateAndGetLength(char value, NpgsqlParameter? parameter) + { + _ = checked((byte)value); + return 1; + } + + /// + public int ValidateAndGetLength(short value, NpgsqlParameter? parameter) + { + _ = checked((byte)value); + return 1; + } + + /// + public int ValidateAndGetLength(int value, NpgsqlParameter? parameter) + { + _ = checked((byte)value); + return 1; + } + + /// + public int ValidateAndGetLength(long value, NpgsqlParameter? parameter) + { + _ = checked((byte)value); + return 1; + } + + /// + public override void Write(char value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteByte((byte)value); + /// + public void Write(byte value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteByte(value); + /// + public void Write(short value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteByte((byte)value); + /// + public void Write(int value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteByte((byte)value); + /// + public void Write(long value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteByte((byte)value); + + #endregion +} + +partial class InternalCharHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + Char converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + Byte converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + Int16 converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + Int32 converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + Int64 converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type InternalCharHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + Char converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Byte converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Int16 converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Int32 converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Int64 converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type InternalCharHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/InternalTypeHandlers/OIDVectorHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/InternalTypeHandlers/OIDVectorHandler.cs new file mode 100644 index 0000000..e449dd6 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/InternalTypeHandlers/OIDVectorHandler.cs @@ -0,0 +1,21 @@ +using System; +using Npgsql.Internal.TypeHandlers.NumericHandlers; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using Npgsql.TypeMapping; +using NpgsqlTypes; + +namespace Npgsql.Internal.TypeHandlers.InternalTypeHandlers; + +/// +/// An OIDVector is simply a regular array of uints, with the sole exception that its lower bound must +/// be 0 (we send 1 for regular arrays). +/// +class OIDVectorHandler : ArrayHandler +{ + public OIDVectorHandler(PostgresType oidvectorType, PostgresType oidType) + : base(oidvectorType, new UInt32Handler(oidType), ArrayNullabilityMode.Never, 0) { } + + public override NpgsqlTypeHandler CreateArrayHandler(PostgresArrayType pgArrayType, ArrayNullabilityMode arrayNullabilityMode) + => new ArrayHandler>(pgArrayType, this, arrayNullabilityMode); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/InternalTypeHandlers/PgLsnHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/InternalTypeHandlers/PgLsnHandler.cs new file mode 100644 index 0000000..e1f2261 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/InternalTypeHandlers/PgLsnHandler.cs @@ -0,0 +1,56 @@ +using System.Diagnostics; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using NpgsqlTypes; + +namespace Npgsql.Internal.TypeHandlers.InternalTypeHandlers; + +partial class PgLsnHandler : NpgsqlSimpleTypeHandler +{ + public PgLsnHandler(PostgresType pgType) : base(pgType) {} + + #region Read + + public override NpgsqlLogSequenceNumber Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + { + Debug.Assert(len == 8); + return new NpgsqlLogSequenceNumber(buf.ReadUInt64()); + } + + #endregion Read + + #region Write + + public override int ValidateAndGetLength(NpgsqlLogSequenceNumber value, NpgsqlParameter? parameter) => 8; + + public override void Write(NpgsqlLogSequenceNumber value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + => buf.WriteUInt64((ulong)value); + + #endregion Write +} + +partial class PgLsnHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + NpgsqlLogSequenceNumber converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type PgLsnHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + NpgsqlLogSequenceNumber converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type PgLsnHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/InternalTypeHandlers/TidHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/InternalTypeHandlers/TidHandler.cs new file mode 100644 index 0000000..ccf50eb --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/InternalTypeHandlers/TidHandler.cs @@ -0,0 +1,64 @@ +using System.Diagnostics; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using NpgsqlTypes; + +namespace Npgsql.Internal.TypeHandlers.InternalTypeHandlers; + +partial class TidHandler : NpgsqlSimpleTypeHandler +{ + public TidHandler(PostgresType pgType) : base(pgType) {} + + #region Read + + public override NpgsqlTid Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + { + Debug.Assert(len == 6); + + var blockNumber = buf.ReadUInt32(); + var offsetNumber = buf.ReadUInt16(); + + return new NpgsqlTid(blockNumber, offsetNumber); + } + + #endregion Read + + #region Write + + public override int ValidateAndGetLength(NpgsqlTid value, NpgsqlParameter? parameter) + => 6; + + public override void Write(NpgsqlTid value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + { + buf.WriteUInt32(value.BlockNumber); + buf.WriteUInt16(value.OffsetNumber); + } + + #endregion Write +} + +partial class TidHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + NpgsqlTid converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type TidHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + NpgsqlTid converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type TidHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/JsonHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/JsonHandler.cs new file mode 100644 index 0000000..6826ff3 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/JsonHandler.cs @@ -0,0 +1,240 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using Npgsql.TypeMapping; +using NpgsqlTypes; + +namespace Npgsql.Internal.TypeHandlers; + +/// +/// A type handler for the PostgreSQL json and jsonb data type. +/// +/// +/// See https://www.postgresql.org/docs/current/datatype-json.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public class JsonHandler : NpgsqlTypeHandler, ITextReaderHandler +{ + readonly JsonSerializerOptions _serializerOptions; + readonly TextHandler _textHandler; + readonly bool _isJsonb; + readonly int _headerLen; + + /// + /// Prepended to the string in the wire encoding + /// + const byte JsonbProtocolVersion = 1; + + static readonly JsonSerializerOptions DefaultSerializerOptions = new(); + + /// + public JsonHandler(PostgresType postgresType, Encoding encoding, bool isJsonb, JsonSerializerOptions? serializerOptions = null) + : base(postgresType) + { + _serializerOptions = serializerOptions ?? DefaultSerializerOptions; + _isJsonb = isJsonb; + _headerLen = isJsonb ? 1 : 0; + _textHandler = new TextHandler(postgresType, encoding); + } + + /// + protected internal override int ValidateAndGetLengthCustom([DisallowNull] TAny value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + { + if (typeof(TAny) == typeof(string) || + typeof(TAny) == typeof(char[]) || + typeof(TAny) == typeof(ArraySegment) || + typeof(TAny) == typeof(char) || + typeof(TAny) == typeof(byte[])) + { + return _textHandler.ValidateAndGetLength(value, ref lengthCache, parameter) + _headerLen; + } + + if (typeof(TAny) == typeof(JsonDocument)) + { + lengthCache ??= new NpgsqlLengthCache(1); + if (lengthCache.IsPopulated) + return lengthCache.Get(); + + var data = SerializeJsonDocument((JsonDocument)(object)value!); + if (parameter != null) + parameter.ConvertedValue = data; + return lengthCache.Set(data.Length + _headerLen); + } + + // User POCO, need to serialize. At least internally ArrayPool buffers are used... + var s = JsonSerializer.Serialize(value, _serializerOptions); + if (parameter != null) + parameter.ConvertedValue = s; + + return _textHandler.ValidateAndGetLength(s, ref lengthCache, parameter) + _headerLen; + } + + /// + protected override async Task WriteWithLengthCustom([DisallowNull] TAny value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + var spaceRequired = _isJsonb ? 5 : 4; + + if (buf.WriteSpaceLeft < spaceRequired) + await buf.Flush(async, cancellationToken); + + buf.WriteInt32(ValidateAndGetLength(value, ref lengthCache, parameter)); + + if (_isJsonb) + buf.WriteByte(JsonbProtocolVersion); + + if (typeof(TAny) == typeof(string)) + await _textHandler.Write((string)(object)value!, buf, lengthCache, parameter, async, cancellationToken); + else if (typeof(TAny) == typeof(char[])) + await _textHandler.Write((char[])(object)value!, buf, lengthCache, parameter, async, cancellationToken); + else if (typeof(TAny) == typeof(ArraySegment)) + await _textHandler.Write((ArraySegment)(object)value!, buf, lengthCache, parameter, async, cancellationToken); + else if (typeof(TAny) == typeof(char)) + await _textHandler.Write((char)(object)value!, buf, lengthCache, parameter, async, cancellationToken); + else if (typeof(TAny) == typeof(byte[])) + await _textHandler.Write((byte[])(object)value!, buf, lengthCache, parameter, async, cancellationToken); + else if (typeof(TAny) == typeof(JsonDocument)) + { + var data = parameter?.ConvertedValue != null + ? (byte[])parameter.ConvertedValue + : SerializeJsonDocument((JsonDocument)(object)value!); + await buf.WriteBytesRaw(data, async, cancellationToken); + } + else + { + // User POCO, read serialized representation from the validation phase + var s = parameter?.ConvertedValue != null + ? (string)parameter.ConvertedValue + : JsonSerializer.Serialize(value!, value!.GetType(), _serializerOptions); + + await _textHandler.Write(s, buf, lengthCache, parameter, async, cancellationToken); + } + } + + /// + public override int ValidateAndGetLength(string value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLengthCustom(value, ref lengthCache, parameter); + + /// + public override async Task Write(string value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + if (_isJsonb) + { + if (buf.WriteSpaceLeft < 1) + await buf.Flush(async, cancellationToken); + buf.WriteByte(JsonbProtocolVersion); + } + + await _textHandler.Write(value, buf, lengthCache, parameter, async, cancellationToken); + } + + /// + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + string s => ValidateAndGetLength(s, ref lengthCache, parameter), + char[] s => ValidateAndGetLength(s, ref lengthCache, parameter), + ArraySegment s => ValidateAndGetLength(s, ref lengthCache, parameter), + char s => ValidateAndGetLength(s, ref lengthCache, parameter), + byte[] s => ValidateAndGetLength(s, ref lengthCache, parameter), + JsonDocument jsonDocument => ValidateAndGetLength(jsonDocument, ref lengthCache, parameter), + _ => ValidateAndGetLength(value, ref lengthCache, parameter) + }; + + /// + public override async Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + // We call into WriteWithLength below, which assumes it as at least enough write space for the length + if (buf.WriteSpaceLeft < 4) + await buf.Flush(async, cancellationToken); + + await (value switch + { + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + string s => WriteWithLengthCustom(s, buf, lengthCache, parameter, async, cancellationToken), + char[] s => WriteWithLengthCustom(s, buf, lengthCache, parameter, async, cancellationToken), + ArraySegment s => WriteWithLengthCustom(s, buf, lengthCache, parameter, async, cancellationToken), + char s => WriteWithLengthCustom(s, buf, lengthCache, parameter, async, cancellationToken), + byte[] s => WriteWithLengthCustom(s, buf, lengthCache, parameter, async, cancellationToken), + JsonDocument jsonDocument => WriteWithLengthCustom(jsonDocument, buf, lengthCache, parameter, async, cancellationToken), + _ => WriteWithLengthCustom(value, buf, lengthCache, parameter, async, cancellationToken), + }); + } + + /// + protected internal override async ValueTask ReadCustom(NpgsqlReadBuffer buf, int byteLen, bool async, FieldDescription? fieldDescription = null) + { + if (_isJsonb) + { + await buf.Ensure(1, async); + var version = buf.ReadByte(); + if (version != JsonbProtocolVersion) + throw new NotSupportedException($"Don't know how to decode JSONB with wire format {version}, your connection is now broken"); + byteLen--; + } + + if (typeof(T) == typeof(string) || + typeof(T) == typeof(char[]) || + typeof(T) == typeof(ArraySegment) || + typeof(T) == typeof(char) || + typeof(T) == typeof(byte[])) + { + return await _textHandler.Read(buf, byteLen, async, fieldDescription); + } + + // JsonDocument is a view over its provided buffer, so we can't return one over our internal buffer (see #2811), so we deserialize + // a string and get a JsonDocument from that. #2818 tracks improving this. + if (typeof(T) == typeof(JsonDocument)) + return (T)(object)JsonDocument.Parse(await _textHandler.Read(buf, byteLen, async, fieldDescription)); + + // User POCO + if (buf.ReadBytesLeft >= byteLen) + return JsonSerializer.Deserialize(buf.ReadSpan(byteLen), _serializerOptions)!; + +#if NET6_0_OR_GREATER + return (async + ? await JsonSerializer.DeserializeAsync(buf.GetStream(byteLen, canSeek: false), _serializerOptions) + : JsonSerializer.Deserialize(buf.GetStream(byteLen, canSeek: false), _serializerOptions))!; +#else + return JsonSerializer.Deserialize(await _textHandler.Read(buf, byteLen, async, fieldDescription), _serializerOptions)!; +#endif + } + + /// + public override ValueTask Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + => ReadCustom(buf, len, async, fieldDescription); + + /// + public TextReader GetTextReader(Stream stream) + { + if (_isJsonb) + { + var version = stream.ReadByte(); + if (version != JsonbProtocolVersion) + throw new NpgsqlException($"Don't know how to decode jsonb with wire format {version}, your connection is now broken"); + } + + return _textHandler.GetTextReader(stream); + } + + byte[] SerializeJsonDocument(JsonDocument document) + { + // TODO: Writing is currently really inefficient - please don't criticize :) + // We need to implement one-pass writing to serialize directly to the buffer (or just switch to pipelines). + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + document.WriteTo(writer); + writer.Flush(); + return stream.ToArray(); + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/JsonPathHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/JsonPathHandler.cs new file mode 100644 index 0000000..cafa83c --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/JsonPathHandler.cs @@ -0,0 +1,99 @@ +using System; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using Npgsql.TypeMapping; +using NpgsqlTypes; + +namespace Npgsql.Internal.TypeHandlers; + +/// +/// A type handler for the PostgreSQL jsonpath data type. +/// +/// +/// See https://www.postgresql.org/docs/current/datatype-json.html#DATATYPE-JSONPATH. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class JsonPathHandler : NpgsqlTypeHandler, ITextReaderHandler +{ + readonly TextHandler _textHandler; + + /// + /// Prepended to the string in the wire encoding + /// + const byte JsonPathVersion = 1; + + /// + protected internal JsonPathHandler(PostgresType postgresType, Encoding encoding) + : base(postgresType) + => _textHandler = new TextHandler(postgresType, encoding); + + /// + public override async ValueTask Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + { + await buf.Ensure(1, async); + + var version = buf.ReadByte(); + if (version != JsonPathVersion) + throw new NotSupportedException($"Don't know how to decode JSONPATH with wire format {version}, your connection is now broken"); + + return await _textHandler.Read(buf, len - 1, async, fieldDescription); + } + + /// + public override int ValidateAndGetLength(string value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) => + 1 + _textHandler.ValidateAndGetLength(value, ref lengthCache, parameter); + + /// + public override async Task Write(string value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + if (buf.WriteSpaceLeft < 1) + await buf.Flush(async, cancellationToken); + + buf.WriteByte(JsonPathVersion); + + await _textHandler.Write(value, buf, lengthCache, parameter, async, cancellationToken); + } + + /// + public TextReader GetTextReader(Stream stream) + { + var version = stream.ReadByte(); + if (version != JsonPathVersion) + throw new NotSupportedException($"Don't know how to decode JSONPATH with wire format {version}, your connection is now broken"); + + return _textHandler.GetTextReader(stream); + } +} + +partial class JsonPathHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + String converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type JsonPathHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + String converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type JsonPathHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/LTreeHandlers/LQueryHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/LTreeHandlers/LQueryHandler.cs new file mode 100644 index 0000000..ca48b51 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/LTreeHandlers/LQueryHandler.cs @@ -0,0 +1,92 @@ +using System; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers.LTreeHandlers; + +/// +/// LQuery binary encoding is a simple UTF8 string, but prepended with a version number. +/// +public class LQueryHandler : TextHandler +{ + /// + /// Prepended to the string in the wire encoding + /// + const byte LQueryProtocolVersion = 1; + + internal override bool PreferTextWrite => false; + + protected internal LQueryHandler(PostgresType postgresType, Encoding encoding) + : base(postgresType, encoding) {} + + #region Write + + public override int ValidateAndGetLength(string value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) => + base.ValidateAndGetLength(value, ref lengthCache, parameter) + 1; + + public override int ValidateAndGetLength(char[] value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) => + base.ValidateAndGetLength(value, ref lengthCache, parameter) + 1; + + + public override int ValidateAndGetLength(ArraySegment value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) => + base.ValidateAndGetLength(value, ref lengthCache, parameter) + 1; + + + public override async Task Write(string value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + if (buf.WriteSpaceLeft < 1) + await buf.Flush(async, cancellationToken); + + buf.WriteByte(LQueryProtocolVersion); + await base.Write(value, buf, lengthCache, parameter, async, cancellationToken); + } + + public override async Task Write(char[] value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + if (buf.WriteSpaceLeft < 1) + await buf.Flush(async, cancellationToken); + + buf.WriteByte(LQueryProtocolVersion); + await base.Write(value, buf, lengthCache, parameter, async, cancellationToken); + } + + public override async Task Write(ArraySegment value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + if (buf.WriteSpaceLeft < 1) + await buf.Flush(async, cancellationToken); + + buf.WriteByte(LQueryProtocolVersion); + await base.Write(value, buf, lengthCache, parameter, async, cancellationToken); + } + + #endregion + + #region Read + + public override async ValueTask Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + { + await buf.Ensure(1, async); + + var version = buf.ReadByte(); + if (version != LQueryProtocolVersion) + throw new NotSupportedException($"Don't know how to decode lquery with wire format {version}, your connection is now broken"); + + return await base.Read(buf, len - 1, async, fieldDescription); + } + + #endregion + + public override TextReader GetTextReader(Stream stream) + { + var version = stream.ReadByte(); + if (version != LQueryProtocolVersion) + throw new NpgsqlException($"Don't know how to decode lquery with wire format {version}, your connection is now broken"); + + return base.GetTextReader(stream); + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/LTreeHandlers/LTreeHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/LTreeHandlers/LTreeHandler.cs new file mode 100644 index 0000000..e0e5093 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/LTreeHandlers/LTreeHandler.cs @@ -0,0 +1,93 @@ +using System; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers.LTreeHandlers; + +/// +/// Ltree binary encoding is a simple UTF8 string, but prepended with a version number. +/// +public class LTreeHandler : TextHandler +{ + /// + /// Prepended to the string in the wire encoding + /// + const byte LtreeProtocolVersion = 1; + + internal override bool PreferTextWrite => false; + + protected internal LTreeHandler(PostgresType postgresType, Encoding encoding) + : base(postgresType, encoding) {} + + #region Write + + public override int ValidateAndGetLength(string value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) => + base.ValidateAndGetLength(value, ref lengthCache, parameter) + 1; + + + public override int ValidateAndGetLength(char[] value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) => + base.ValidateAndGetLength(value, ref lengthCache, parameter) + 1; + + + public override int ValidateAndGetLength(ArraySegment value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) => + base.ValidateAndGetLength(value, ref lengthCache, parameter) + 1; + + + public override async Task Write(string value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + if (buf.WriteSpaceLeft < 1) + await buf.Flush(async, cancellationToken); + + buf.WriteByte(LtreeProtocolVersion); + await base.Write(value, buf, lengthCache, parameter, async, cancellationToken); + } + + public override async Task Write(char[] value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + if (buf.WriteSpaceLeft < 1) + await buf.Flush(async, cancellationToken); + + buf.WriteByte(LtreeProtocolVersion); + await base.Write(value, buf, lengthCache, parameter, async, cancellationToken); + } + + public override async Task Write(ArraySegment value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + if (buf.WriteSpaceLeft < 1) + await buf.Flush(async, cancellationToken); + + buf.WriteByte(LtreeProtocolVersion); + await base.Write(value, buf, lengthCache, parameter, async, cancellationToken); + } + + #endregion + + #region Read + + public override async ValueTask Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + { + await buf.Ensure(1, async); + + var version = buf.ReadByte(); + if (version != LtreeProtocolVersion) + throw new NotSupportedException($"Don't know how to decode ltree with wire format {version}, your connection is now broken"); + + return await base.Read(buf, len - 1, async, fieldDescription); + } + + #endregion + + public override TextReader GetTextReader(Stream stream) + { + var version = stream.ReadByte(); + if (version != LtreeProtocolVersion) + throw new NpgsqlException($"Don't know how to decode ltree with wire format {version}, your connection is now broken"); + + return base.GetTextReader(stream); + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/LTreeHandlers/LTxtQueryHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/LTreeHandlers/LTxtQueryHandler.cs new file mode 100644 index 0000000..2fd8f7c --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/LTreeHandlers/LTxtQueryHandler.cs @@ -0,0 +1,93 @@ +using System; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers.LTreeHandlers; + +/// +/// LTxtQuery binary encoding is a simple UTF8 string, but prepended with a version number. +/// +public class LTxtQueryHandler : TextHandler +{ + /// + /// Prepended to the string in the wire encoding + /// + const byte LTxtQueryProtocolVersion = 1; + + internal override bool PreferTextWrite => false; + + protected internal LTxtQueryHandler(PostgresType postgresType, Encoding encoding) + : base(postgresType, encoding) {} + + #region Write + + public override int ValidateAndGetLength(string value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) => + base.ValidateAndGetLength(value, ref lengthCache, parameter) + 1; + + + public override int ValidateAndGetLength(char[] value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) => + base.ValidateAndGetLength(value, ref lengthCache, parameter) + 1; + + + public override int ValidateAndGetLength(ArraySegment value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) => + base.ValidateAndGetLength(value, ref lengthCache, parameter) + 1; + + + public override async Task Write(string value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + if (buf.WriteSpaceLeft < 1) + await buf.Flush(async, cancellationToken); + + buf.WriteByte(LTxtQueryProtocolVersion); + await base.Write(value, buf, lengthCache, parameter, async, cancellationToken); + } + + public override async Task Write(char[] value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + if (buf.WriteSpaceLeft < 1) + await buf.Flush(async, cancellationToken); + + buf.WriteByte(LTxtQueryProtocolVersion); + await base.Write(value, buf, lengthCache, parameter, async, cancellationToken); + } + + public override async Task Write(ArraySegment value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + if (buf.WriteSpaceLeft < 1) + await buf.Flush(async, cancellationToken); + + buf.WriteByte(LTxtQueryProtocolVersion); + await base.Write(value, buf, lengthCache, parameter, async, cancellationToken); + } + + #endregion + + #region Read + + public override async ValueTask Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + { + await buf.Ensure(1, async); + + var version = buf.ReadByte(); + if (version != LTxtQueryProtocolVersion) + throw new NotSupportedException($"Don't know how to decode ltxtquery with wire format {version}, your connection is now broken"); + + return await base.Read(buf, len - 1, async, fieldDescription); + } + + #endregion + + public override TextReader GetTextReader(Stream stream) + { + var version = stream.ReadByte(); + if (version != LTxtQueryProtocolVersion) + throw new NpgsqlException($"Don't know how to decode ltxtquery with wire format {version}, your connection is now broken"); + + return base.GetTextReader(stream); + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/MultirangeHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/MultirangeHandler.cs new file mode 100644 index 0000000..f295822 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/MultirangeHandler.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using NpgsqlTypes; + +namespace Npgsql.Internal.TypeHandlers; + +public partial class MultirangeHandler : NpgsqlTypeHandler[]>, + INpgsqlTypeHandler>> +{ + /// + /// The type handler for the range that this multirange type holds + /// + protected RangeHandler RangeHandler { get; } + + /// + public MultirangeHandler(PostgresMultirangeType pgMultirangeType, RangeHandler rangeHandler) + : base(pgMultirangeType) + => RangeHandler = rangeHandler; + + public override ValueTask[]> Read( + NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + => ReadMultirangeArray(buf, len, async, fieldDescription); + + protected async ValueTask[]> ReadMultirangeArray( + NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + { + await buf.Ensure(4, async); + var numRanges = buf.ReadInt32(); + var multirange = new NpgsqlRange[numRanges]; + + for (var i = 0; i < numRanges; i++) + { + await buf.Ensure(4, async); + var rangeLen = buf.ReadInt32(); + multirange[i] = await RangeHandler.ReadRange(buf, rangeLen, async, fieldDescription); + } + + return multirange; + } + + ValueTask>> INpgsqlTypeHandler>>.Read( + NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + => ReadMultirangeList(buf, len, async, fieldDescription); + + protected async ValueTask>> ReadMultirangeList( + NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + { + await buf.Ensure(4, async); + var numRanges = buf.ReadInt32(); + var multirange = new List>(numRanges); + + for (var i = 0; i < numRanges; i++) + { + await buf.Ensure(4, async); + var rangeLen = buf.ReadInt32(); + multirange.Add(await RangeHandler.ReadRange(buf, rangeLen, async, fieldDescription)); + } + + return multirange; + } + + public override int ValidateAndGetLength(NpgsqlRange[] value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLengthMultirange(value, ref lengthCache, parameter); + + public int ValidateAndGetLength(List> value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLengthMultirange(value, ref lengthCache, parameter); + + protected int ValidateAndGetLengthMultirange( + IList> value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + { + lengthCache ??= new NpgsqlLengthCache(1); + if (lengthCache.IsPopulated) + return lengthCache.Get(); + + // Leave empty slot for the entire array length, and go ahead an populate the element slots + var pos = lengthCache.Position; + lengthCache.Set(0); + + var sum = 4 + 4 * value.Count; + for (var i = 0; i < value.Count; i++) + sum += RangeHandler.ValidateAndGetLength(value[i], ref lengthCache, parameter); + + lengthCache.Lengths[pos] = sum; + return sum; + } + + public override Task Write( + NpgsqlRange[] value, + NpgsqlWriteBuffer buf, + NpgsqlLengthCache? lengthCache, + NpgsqlParameter? parameter, + bool async, + CancellationToken cancellationToken = default) + => WriteMultirange(value, buf, lengthCache, parameter, async, cancellationToken); + + public Task Write( + List> value, + NpgsqlWriteBuffer buf, + NpgsqlLengthCache? lengthCache, + NpgsqlParameter? parameter, + bool async, + CancellationToken cancellationToken = default) + => WriteMultirange(value, buf, lengthCache, parameter, async, cancellationToken); + + public async Task WriteMultirange( + IList> value, + NpgsqlWriteBuffer buf, + NpgsqlLengthCache? lengthCache, + NpgsqlParameter? parameter, + bool async, + CancellationToken cancellationToken = default) + { + if (buf.WriteSpaceLeft < 4) + await buf.Flush(async, cancellationToken); + + buf.WriteInt32(value.Count); + + for (var i = 0; i < value.Count; i++) + await RangeHandler.WriteWithLength(value[i], buf, lengthCache, parameter: null, async, cancellationToken); + } +} + +public class MultirangeHandler : MultirangeHandler, + INpgsqlTypeHandler[]>, INpgsqlTypeHandler>> +{ + /// + public MultirangeHandler(PostgresMultirangeType pgMultirangeType, RangeHandler rangeHandler) + : base(pgMultirangeType, rangeHandler) {} + + ValueTask[]> INpgsqlTypeHandler[]>.Read( + NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + => ReadMultirangeArray(buf, len, async, fieldDescription); + + ValueTask>> INpgsqlTypeHandler>>.Read( + NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + => ReadMultirangeList(buf, len, async, fieldDescription); + + public int ValidateAndGetLength(List> value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLengthMultirange(value, ref lengthCache, parameter); + + public int ValidateAndGetLength(NpgsqlRange[] value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLengthMultirange(value, ref lengthCache, parameter); + + public Task Write( + List> value, + NpgsqlWriteBuffer buf, + NpgsqlLengthCache? lengthCache, + NpgsqlParameter? parameter, + bool async, + CancellationToken cancellationToken = default) + => WriteMultirange(value, buf, lengthCache, parameter, async, cancellationToken); + + public Task Write( + NpgsqlRange[] value, + NpgsqlWriteBuffer buf, + NpgsqlLengthCache? lengthCache, + NpgsqlParameter? parameter, + bool async, + CancellationToken cancellationToken = default) + => WriteMultirange(value, buf, lengthCache, parameter, async, cancellationToken); + + public override int ValidateObjectAndGetLength(object? value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + NpgsqlRange[] converted => ((INpgsqlTypeHandler[]>)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + NpgsqlRange[] converted => ((INpgsqlTypeHandler[]>)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + List> converted => ((INpgsqlTypeHandler>>)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + List> converted => ((INpgsqlTypeHandler>>)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + DBNull => 0, + null => 0, + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type RangeHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + NpgsqlRange[] converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + NpgsqlRange[] converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + List> converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + List> converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type RangeHandler") + }; +} + +partial class MultirangeHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + NpgsqlRange[] converted => ((INpgsqlTypeHandler[]>)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + List> converted => ((INpgsqlTypeHandler>>)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type MultirangeHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + NpgsqlRange[] converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + List> converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type MultirangeHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/NetworkHandlers/CidrHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/NetworkHandlers/CidrHandler.cs new file mode 100644 index 0000000..d5f0e0b --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/NetworkHandlers/CidrHandler.cs @@ -0,0 +1,79 @@ +using System.Net; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using NpgsqlTypes; + +#pragma warning disable 618 + +namespace Npgsql.Internal.TypeHandlers.NetworkHandlers; + +/// +/// A type handler for the PostgreSQL cidr data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-net-types.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class CidrHandler : NpgsqlSimpleTypeHandler<(IPAddress Address, int Subnet)>, INpgsqlSimpleTypeHandler +{ + public CidrHandler(PostgresType pgType) : base(pgType) {} + + /// + public override (IPAddress Address, int Subnet) Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => InetHandler.DoRead(buf, len, fieldDescription, true); + + NpgsqlInet INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + { + var (address, subnet) = Read(buf, len, fieldDescription); + return new NpgsqlInet(address, subnet); + } + + /// + public override int ValidateAndGetLength((IPAddress Address, int Subnet) value, NpgsqlParameter? parameter) + => InetHandler.GetLength(value.Address); + + /// + public int ValidateAndGetLength(NpgsqlInet value, NpgsqlParameter? parameter) + => InetHandler.GetLength(value.Address); + + /// + public override void Write((IPAddress Address, int Subnet) value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + => InetHandler.DoWrite(value.Address, value.Subnet, buf, true); + + /// + public void Write(NpgsqlInet value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + => InetHandler.DoWrite(value.Address, value.Netmask, buf, true); +} + +partial class CidrHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + ValueTuple converted => ((INpgsqlSimpleTypeHandler>)this).ValidateAndGetLength(converted, parameter), + + NpgsqlInet converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type CidrHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + ValueTuple converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + NpgsqlInet converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type CidrHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/NetworkHandlers/InetHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/NetworkHandlers/InetHandler.cs new file mode 100644 index 0000000..075edae --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/NetworkHandlers/InetHandler.cs @@ -0,0 +1,164 @@ +using System; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using NpgsqlTypes; + +#pragma warning disable 618 + +namespace Npgsql.Internal.TypeHandlers.NetworkHandlers; + +/// +/// A type handler for the PostgreSQL cidr data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-net-types.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class InetHandler : NpgsqlSimpleTypeHandlerWithPsv, + INpgsqlSimpleTypeHandler +{ + // ReSharper disable InconsistentNaming + const byte IPv4 = 2; + const byte IPv6 = 3; + // ReSharper restore InconsistentNaming + + public InetHandler(PostgresType pgType) : base(pgType) {} + + #region Read + + /// + public override IPAddress Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => DoRead(buf, len, fieldDescription, false).Address; + +#pragma warning disable CA1801 // Review unused parameters + internal static (IPAddress Address, int Subnet) DoRead( + NpgsqlReadBuffer buf, + int len, + FieldDescription? fieldDescription, + bool isCidrHandler) + { + buf.ReadByte(); // addressFamily + var mask = buf.ReadByte(); + var isCidr = buf.ReadByte() == 1; + Debug.Assert(isCidrHandler == isCidr); + var numBytes = buf.ReadByte(); + var bytes = new byte[numBytes]; + for (var i = 0; i < numBytes; i++) + bytes[i] = buf.ReadByte(); + + return (new IPAddress(bytes), mask); + } +#pragma warning restore CA1801 // Review unused parameters + + /// + protected override (IPAddress Address, int Subnet) ReadPsv(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => DoRead(buf, len, fieldDescription, false); + + NpgsqlInet INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + { + var (address, subnet) = DoRead(buf, len, fieldDescription, false); + return new NpgsqlInet(address, subnet); + } + + #endregion Read + + #region Write + + /// + public override int ValidateAndGetLength(IPAddress value, NpgsqlParameter? parameter) + => GetLength(value); + + /// + public override int ValidateAndGetLength((IPAddress Address, int Subnet) value, NpgsqlParameter? parameter) + => GetLength(value.Address); + + /// + public int ValidateAndGetLength(NpgsqlInet value, NpgsqlParameter? parameter) + => GetLength(value.Address); + + /// + public override void Write(IPAddress value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + => DoWrite(value, -1, buf, false); + + /// + public override void Write((IPAddress Address, int Subnet) value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + => DoWrite(value.Address, value.Subnet, buf, false); + + /// + public void Write(NpgsqlInet value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + => DoWrite(value.Address, value.Netmask, buf, false); + + internal static void DoWrite(IPAddress ip, int mask, NpgsqlWriteBuffer buf, bool isCidrHandler) + { + switch (ip.AddressFamily) { + case AddressFamily.InterNetwork: + buf.WriteByte(IPv4); + if (mask == -1) + mask = 32; + break; + case AddressFamily.InterNetworkV6: + buf.WriteByte(IPv6); + if (mask == -1) + mask = 128; + break; + default: + throw new InvalidCastException($"Can't handle IPAddress with AddressFamily {ip.AddressFamily}, only InterNetwork or InterNetworkV6!"); + } + + buf.WriteByte((byte)mask); + buf.WriteByte((byte)(isCidrHandler ? 1 : 0)); // Ignored on server side + var bytes = ip.GetAddressBytes(); + buf.WriteByte((byte)bytes.Length); + buf.WriteBytes(bytes, 0, bytes.Length); + } + + internal static int GetLength(IPAddress value) + => value.AddressFamily switch + { + AddressFamily.InterNetwork => 8, + AddressFamily.InterNetworkV6 => 20, + _ => throw new InvalidCastException($"Can't handle IPAddress with AddressFamily {value.AddressFamily}, only InterNetwork or InterNetworkV6!") + }; + + #endregion Write +} + +partial class InetHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + IPAddress converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + ValueTuple converted => ((INpgsqlSimpleTypeHandler>)this).ValidateAndGetLength(converted, parameter), + + NpgsqlInet converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type InetHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + IPAddress converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + ValueTuple converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + NpgsqlInet converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type InetHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/NetworkHandlers/MacaddrHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/NetworkHandlers/MacaddrHandler.cs new file mode 100644 index 0000000..ee89d08 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/NetworkHandlers/MacaddrHandler.cs @@ -0,0 +1,77 @@ +using System.Diagnostics; +using System.Net.NetworkInformation; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers.NetworkHandlers; + +/// +/// A type handler for the PostgreSQL macaddr and macaddr8 data types. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-net-types.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class MacaddrHandler : NpgsqlSimpleTypeHandler +{ + public MacaddrHandler(PostgresType pgType) : base(pgType) {} + + #region Read + + /// + public override PhysicalAddress Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + { + Debug.Assert(len == 6 || len == 8); + + var bytes = new byte[len]; + + buf.ReadBytes(bytes, 0, len); + return new PhysicalAddress(bytes); + } + + #endregion Read + + #region Write + + /// + public override int ValidateAndGetLength(PhysicalAddress value, NpgsqlParameter? parameter) + => value.GetAddressBytes().Length; + + /// + public override void Write(PhysicalAddress value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + { + var bytes = value.GetAddressBytes(); + buf.WriteBytes(bytes, 0, bytes.Length); + } + + #endregion Write +} + +partial class MacaddrHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + PhysicalAddress converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type MacaddrHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + PhysicalAddress converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type MacaddrHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/DecimalRaw.cs b/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/DecimalRaw.cs new file mode 100644 index 0000000..86d95d7 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/DecimalRaw.cs @@ -0,0 +1,150 @@ +using System; +using System.Runtime.InteropServices; + +namespace Npgsql.Internal.TypeHandlers.NumericHandlers; + +[StructLayout(LayoutKind.Explicit)] +struct DecimalRaw +{ + const int SignMask = unchecked((int)0x80000000); + const int ScaleMask = 0x00FF0000; + const int ScaleShift = 16; + + // Fast access for 10^n where n is 0-9 + internal static readonly uint[] Powers10 = new uint[] + { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000 + }; + + // The maximum power of 10 that a 32 bit unsigned integer can store + internal static readonly int MaxUInt32Scale = Powers10.Length - 1; + + // Do not change the order in which these fields are declared. It + // should be same as in the System.Decimal struct. + [FieldOffset(0)] + decimal _value; + [FieldOffset(0)] + int _flags; + [FieldOffset(4)] + uint _high; + [FieldOffset(8)] + uint _low; + [FieldOffset(12)] + uint _mid; + + public bool Negative => (_flags & SignMask) != 0; + + public int Scale + { + get => (_flags & ScaleMask) >> ScaleShift; + set => _flags = (_flags & SignMask) | ((value << ScaleShift) & ScaleMask); + } + + public uint High => _high; + public uint Mid => _mid; + public uint Low => _low; + public decimal Value => _value; + + public DecimalRaw(decimal value) : this() => _value = value; + + public DecimalRaw(long value) : this() + { + if (value >= 0) + _flags = 0; + else + { + _flags = SignMask; + value = -value; + } + + _low = (uint)value; + _mid = (uint)(value >> 32); + _high = 0; + } + + public static void Negate(ref DecimalRaw value) + => value._flags ^= SignMask; + + public static void Add(ref DecimalRaw value, uint addend) + { + uint integer; + uint sum; + + integer = value._low; + value._low = sum = integer + addend; + + if (sum >= integer && sum >= addend) + return; + + integer = value._mid; + value._mid = sum = integer + 1; + + if (sum >= integer && sum >= 1) + return; + + integer = value._high; + value._high = sum = integer + 1; + + if (sum < integer || sum < 1) + throw new OverflowException("Numeric value does not fit in a System.Decimal"); + } + + public static void Multiply(ref DecimalRaw value, uint multiplier) + { + ulong integer; + uint remainder; + + integer = (ulong)value._low * multiplier; + value._low = (uint)integer; + remainder = (uint)(integer >> 32); + + integer = (ulong)value._mid * multiplier + remainder; + value._mid = (uint)integer; + remainder = (uint)(integer >> 32); + + integer = (ulong)value._high * multiplier + remainder; + value._high = (uint)integer; + remainder = (uint)(integer >> 32); + + if (remainder != 0) + throw new OverflowException("Numeric value does not fit in a System.Decimal"); + } + + public static uint Divide(ref DecimalRaw value, uint divisor) + { + ulong integer; + uint remainder = 0; + + if (value._high != 0) + { + integer = value._high; + value._high = (uint)(integer / divisor); + remainder = (uint)(integer % divisor); + } + + if (value._mid != 0 || remainder != 0) + { + integer = ((ulong)remainder << 32) | value._mid; + value._mid = (uint)(integer / divisor); + remainder = (uint)(integer % divisor); + } + + if (value._low != 0 || remainder != 0) + { + integer = ((ulong)remainder << 32) | value._low; + value._low = (uint)(integer / divisor); + remainder = (uint)(integer % divisor); + } + + return remainder; + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/DoubleHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/DoubleHandler.cs new file mode 100644 index 0000000..4543c54 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/DoubleHandler.cs @@ -0,0 +1,57 @@ +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers.NumericHandlers; + +/// +/// A type handler for the PostgreSQL double precision data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-numeric.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class DoubleHandler : NpgsqlSimpleTypeHandler +{ + public DoubleHandler(PostgresType pgType) : base(pgType) {} + + /// + public override double Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => buf.ReadDouble(); + + /// + public override int ValidateAndGetLength(double value, NpgsqlParameter? parameter) + => 8; + + /// + public override void Write(double value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + => buf.WriteDouble(value); +} + +partial class DoubleHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + Double converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type DoubleHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + Double converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type DoubleHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/Int16Handler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/Int16Handler.cs new file mode 100644 index 0000000..39b53bb --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/Int16Handler.cs @@ -0,0 +1,162 @@ +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers.NumericHandlers; + +/// +/// A type handler for the PostgreSQL smallint data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-numeric.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class Int16Handler : NpgsqlSimpleTypeHandler, + INpgsqlSimpleTypeHandler, INpgsqlSimpleTypeHandler, INpgsqlSimpleTypeHandler, INpgsqlSimpleTypeHandler, + INpgsqlSimpleTypeHandler, INpgsqlSimpleTypeHandler, INpgsqlSimpleTypeHandler +{ + public Int16Handler(PostgresType pgType) : base(pgType) {} + + #region Read + + /// + public override short Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => buf.ReadInt16(); + + byte INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => checked((byte)Read(buf, len, fieldDescription)); + + sbyte INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => checked((sbyte)Read(buf, len, fieldDescription)); + + int INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => Read(buf, len, fieldDescription); + + long INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => Read(buf, len, fieldDescription); + + float INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => Read(buf, len, fieldDescription); + + double INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => Read(buf, len, fieldDescription); + + decimal INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => Read(buf, len, fieldDescription); + + #endregion Read + + #region Write + + /// + public override int ValidateAndGetLength(short value, NpgsqlParameter? parameter) => 2; + /// + public int ValidateAndGetLength(byte value, NpgsqlParameter? parameter) => 2; + /// + public int ValidateAndGetLength(sbyte value, NpgsqlParameter? parameter) => 2; + /// + public int ValidateAndGetLength(decimal value, NpgsqlParameter? parameter) => 2; + + /// + public int ValidateAndGetLength(int value, NpgsqlParameter? parameter) + { + _ = checked((short)value); + return 2; + } + + /// + public int ValidateAndGetLength(long value, NpgsqlParameter? parameter) + { + _ = checked((short)value); + return 2; + } + + /// + public int ValidateAndGetLength(float value, NpgsqlParameter? parameter) + { + _ = checked((short)value); + return 2; + } + + /// + public int ValidateAndGetLength(double value, NpgsqlParameter? parameter) + { + _ = checked((short)value); + return 2; + } + + /// + public override void Write(short value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteInt16(value); + /// + public void Write(int value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteInt16((short)value); + /// + public void Write(long value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteInt16((short)value); + /// + public void Write(byte value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteInt16(value); + /// + public void Write(sbyte value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteInt16(value); + /// + public void Write(decimal value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteInt16((short)value); + /// + public void Write(double value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteInt16((short)value); + /// + public void Write(float value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteInt16((short)value); + + #endregion Write +} + +partial class Int16Handler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + Int16 converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + Byte converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + SByte converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + Int32 converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + Int64 converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + Single converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + Double converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + Decimal converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type Int16Handler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + Int16 converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Byte converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + SByte converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Int32 converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Int64 converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Single converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Double converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Decimal converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type Int16Handler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/Int32Handler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/Int32Handler.cs new file mode 100644 index 0000000..fbef3cf --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/Int32Handler.cs @@ -0,0 +1,145 @@ +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers.NumericHandlers; + +/// +/// A type handler for the PostgreSQL integer data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-numeric.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class Int32Handler : NpgsqlSimpleTypeHandler, INpgsqlSimpleTypeHandler, + INpgsqlSimpleTypeHandler, INpgsqlSimpleTypeHandler, INpgsqlSimpleTypeHandler, + INpgsqlSimpleTypeHandler, INpgsqlSimpleTypeHandler, INpgsqlSimpleTypeHandler +{ + public Int32Handler(PostgresType pgType) : base(pgType) {} + + #region Read + + public override int Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => buf.ReadInt32(); + + byte INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => checked((byte)Read(buf, len, fieldDescription)); + + short INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => checked((short)Read(buf, len, fieldDescription)); + + long INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => Read(buf, len, fieldDescription); + + float INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => Read(buf, len, fieldDescription); + + double INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => Read(buf, len, fieldDescription); + + decimal INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => Read(buf, len, fieldDescription); + + #endregion Read + + #region Write + + /// + public override int ValidateAndGetLength(int value, NpgsqlParameter? parameter) => 4; + /// + public int ValidateAndGetLength(short value, NpgsqlParameter? parameter) => 4; + /// + public int ValidateAndGetLength(byte value, NpgsqlParameter? parameter) => 4; + /// + public int ValidateAndGetLength(decimal value, NpgsqlParameter? parameter) => 4; + + /// + public int ValidateAndGetLength(long value, NpgsqlParameter? parameter) + { + _ = checked((int)value); + return 4; + } + + /// + public int ValidateAndGetLength(float value, NpgsqlParameter? parameter) + { + _ = checked((int)value); + return 4; + } + + /// + public int ValidateAndGetLength(double value, NpgsqlParameter? parameter) + { + _ = checked((int)value); + return 4; + } + + /// + public override void Write(int value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteInt32(value); + /// + public void Write(short value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteInt32(value); + /// + public void Write(long value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteInt32((int)value); + /// + public void Write(byte value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteInt32(value); + /// + public void Write(float value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteInt32((int)value); + /// + public void Write(double value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteInt32((int)value); + /// + public void Write(decimal value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteInt32((int)value); + + #endregion Write +} + +partial class Int32Handler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + Int32 converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + Byte converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + Int16 converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + Int64 converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + Single converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + Double converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + Decimal converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type Int32Handler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + Int32 converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Byte converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Int16 converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Int64 converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Single converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Double converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Decimal converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type Int32Handler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/Int64Handler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/Int64Handler.cs new file mode 100644 index 0000000..abd5d32 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/Int64Handler.cs @@ -0,0 +1,141 @@ +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers.NumericHandlers; + +/// +/// A type handler for the PostgreSQL bigint data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-numeric.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class Int64Handler : NpgsqlSimpleTypeHandler, + INpgsqlSimpleTypeHandler, INpgsqlSimpleTypeHandler, INpgsqlSimpleTypeHandler, + INpgsqlSimpleTypeHandler, INpgsqlSimpleTypeHandler, INpgsqlSimpleTypeHandler +{ + public Int64Handler(PostgresType pgType) : base(pgType) {} + + #region Read + + /// + public override long Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => buf.ReadInt64(); + + byte INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => checked((byte)Read(buf, len, fieldDescription)); + + short INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => checked((short)Read(buf, len, fieldDescription)); + + int INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => checked((int)Read(buf, len, fieldDescription)); + + float INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => Read(buf, len, fieldDescription); + + double INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => Read(buf, len, fieldDescription); + + decimal INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => Read(buf, len, fieldDescription); + + #endregion Read + + #region Write + + /// + public override int ValidateAndGetLength(long value, NpgsqlParameter? parameter) => 8; + /// + public int ValidateAndGetLength(int value, NpgsqlParameter? parameter) => 8; + /// + public int ValidateAndGetLength(short value, NpgsqlParameter? parameter) => 8; + /// + public int ValidateAndGetLength(byte value, NpgsqlParameter? parameter) => 8; + /// + public int ValidateAndGetLength(decimal value, NpgsqlParameter? parameter) => 8; + + /// + public int ValidateAndGetLength(float value, NpgsqlParameter? parameter) + { + _ = checked((long)value); + return 8; + } + + /// + public int ValidateAndGetLength(double value, NpgsqlParameter? parameter) + { + _ = checked((long)value); + return 8; + } + + /// + public override void Write(long value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteInt64(value); + /// + public void Write(short value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteInt64(value); + /// + public void Write(int value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteInt64(value); + /// + public void Write(byte value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteInt64(value); + /// + public void Write(float value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteInt64((long)value); + /// + public void Write(double value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteInt64((long)value); + /// + public void Write(decimal value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteInt64((long)value); + + #endregion Write +} + +partial class Int64Handler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + Int64 converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + Byte converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + Int16 converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + Int32 converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + Single converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + Double converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + Decimal converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type Int64Handler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + Int64 converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Byte converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Int16 converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Int32 converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Single converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Double converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Decimal converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type Int64Handler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/MoneyHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/MoneyHandler.cs new file mode 100644 index 0000000..5b2aedf --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/MoneyHandler.cs @@ -0,0 +1,77 @@ +using System; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers.NumericHandlers; + +/// +/// A type handler for the PostgreSQL money data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-money.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class MoneyHandler : NpgsqlSimpleTypeHandler +{ + public MoneyHandler(PostgresType pgType) : base(pgType) {} + + const int MoneyScale = 2; + + /// + public override decimal Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => new DecimalRaw(buf.ReadInt64()) { Scale = MoneyScale }.Value; + + /// + public override int ValidateAndGetLength(decimal value, NpgsqlParameter? parameter) + => value < -92233720368547758.08M || value > 92233720368547758.07M + ? throw new OverflowException($"The supplied value ({value}) is outside the range for a PostgreSQL money value.") + : 8; + + /// + public override void Write(decimal value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + { + var raw = new DecimalRaw(value); + + var scaleDifference = MoneyScale - raw.Scale; + if (scaleDifference > 0) + DecimalRaw.Multiply(ref raw, DecimalRaw.Powers10[scaleDifference]); + else + { + value = Math.Round(value, MoneyScale, MidpointRounding.AwayFromZero); + raw = new DecimalRaw(value); + } + + var result = (long)raw.Mid << 32 | raw.Low; + if (raw.Negative) result = -result; + buf.WriteInt64(result); + } +} + +partial class MoneyHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + Decimal converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type MoneyHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + Decimal converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type MoneyHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/NumericHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/NumericHandler.cs new file mode 100644 index 0000000..d9d3c7d --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/NumericHandler.cs @@ -0,0 +1,486 @@ +using System; +using System.Globalization; +using System.Numerics; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers.NumericHandlers; + +/// +/// A type handler for the PostgreSQL numeric data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-numeric.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class NumericHandler : NpgsqlTypeHandler, + INpgsqlTypeHandler, INpgsqlTypeHandler, INpgsqlTypeHandler, INpgsqlTypeHandler, + INpgsqlTypeHandler, INpgsqlTypeHandler, INpgsqlTypeHandler +{ + public NumericHandler(PostgresType pgType) : base(pgType) {} + + const int MaxDecimalScale = 28; + + const int SignPositive = 0x0000; + const int SignNegative = 0x4000; + const int SignNan = 0xC000; + const int SignPinf = 0xD000; + const int SignNinf = 0xF000; + const int SignSpecialMask = 0xC000; + + const int MaxGroupCount = 8; + const int MaxGroupScale = 4; + + static readonly uint MaxGroupSize = DecimalRaw.Powers10[MaxGroupScale]; + + #region Read + + /// + public override async ValueTask Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + { + await buf.Ensure(4 * sizeof(short), async); + var result = new DecimalRaw(); + var groups = buf.ReadInt16(); + var weight = buf.ReadInt16() - groups + 1; + var sign = buf.ReadUInt16(); + + if ((sign & SignSpecialMask) == SignSpecialMask) + { + throw sign switch + { + SignNan => new InvalidCastException("Numeric NaN not supported by System.Decimal"), + SignPinf => new InvalidCastException("Numeric Infinity not supported by System.Decimal"), + SignNinf => new InvalidCastException("Numeric -Infinity not supported by System.Decimal"), + _ => new InvalidCastException($"Numeric special value {sign} not supported by System.Decimal") + }; + } + + if (sign == SignNegative) + DecimalRaw.Negate(ref result); + + var scale = buf.ReadInt16(); + if (scale < 0 is var exponential && exponential) + scale = (short)(-scale); + else + result.Scale = scale; + + if (scale > MaxDecimalScale) + throw new OverflowException("Numeric value does not fit in a System.Decimal"); + + var scaleDifference = exponential + ? weight * MaxGroupScale + : weight * MaxGroupScale + scale; + + if (groups > MaxGroupCount) + throw new OverflowException("Numeric value does not fit in a System.Decimal"); + + await buf.Ensure(groups * sizeof(ushort), async); + + if (groups == MaxGroupCount) + { + while (groups-- > 1) + { + DecimalRaw.Multiply(ref result, MaxGroupSize); + DecimalRaw.Add(ref result, buf.ReadUInt16()); + } + + var group = buf.ReadUInt16(); + var groupSize = DecimalRaw.Powers10[-scaleDifference]; + if (group % groupSize != 0) + throw new OverflowException("Numeric value does not fit in a System.Decimal"); + + DecimalRaw.Multiply(ref result, MaxGroupSize / groupSize); + DecimalRaw.Add(ref result, group / groupSize); + } + else + { + while (groups-- > 0) + { + DecimalRaw.Multiply(ref result, MaxGroupSize); + DecimalRaw.Add(ref result, buf.ReadUInt16()); + } + + if (scaleDifference < 0) + DecimalRaw.Divide(ref result, DecimalRaw.Powers10[-scaleDifference]); + else + while (scaleDifference > 0) + { + var scaleChunk = Math.Min(DecimalRaw.MaxUInt32Scale, scaleDifference); + DecimalRaw.Multiply(ref result, DecimalRaw.Powers10[scaleChunk]); + scaleDifference -= scaleChunk; + } + } + + return result.Value; + } + + async ValueTask INpgsqlTypeHandler.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + => (byte)await Read(buf, len, async, fieldDescription); + + async ValueTask INpgsqlTypeHandler.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + => (short)await Read(buf, len, async, fieldDescription); + + async ValueTask INpgsqlTypeHandler.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + => (int)await Read(buf, len, async, fieldDescription); + + async ValueTask INpgsqlTypeHandler.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + => (long)await Read(buf, len, async, fieldDescription); + + async ValueTask INpgsqlTypeHandler.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + => (float)await Read(buf, len, async, fieldDescription); + + async ValueTask INpgsqlTypeHandler.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + => (double)await Read(buf, len, async, fieldDescription); + + async ValueTask INpgsqlTypeHandler.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + { + await buf.Ensure(4 * sizeof(short), async); + + var groups = (int)buf.ReadUInt16(); + var weightLeft = (int)buf.ReadInt16(); + var weightRight = weightLeft - groups + 1; + var sign = buf.ReadUInt16(); + buf.ReadInt16(); // dscale + + if (groups == 0) + { + return sign switch + { + SignPositive or SignNegative => BigInteger.Zero, + SignNan => throw new InvalidCastException("Numeric NaN not supported by BigInteger"), + SignPinf => throw new InvalidCastException("Numeric Infinity not supported by BigInteger"), + SignNinf => throw new InvalidCastException("Numeric -Infinity not supported by BigInteger"), + _ => throw new InvalidCastException($"Numeric special value {sign} not supported") + }; + } + + if (weightRight < 0) + { + await buf.Skip(groups * sizeof(ushort), async); + throw new InvalidCastException("Numeric value with non-zero fractional digits not supported by BigInteger"); + } + + var digits = new ushort[groups]; + + for (var i = 0; i < groups; i++) + { + await buf.Ensure(sizeof(ushort), async); + digits[i] = buf.ReadUInt16(); + } + + // Calculate powers 10^8, 10^16, 10^32, ... + // We should have the last calculated power to be less than the input + var lenPow = 2; // 2 ushorts fit in one uint, represents 10^8 + var numPowers = 0; + while (lenPow < weightLeft + 1) + { + lenPow <<= 1; + ++numPowers; + } + var factors = numPowers > 0 ? new BigInteger[numPowers] : null; + if (numPowers > 0) + { + factors![0] = new BigInteger(100000000U); + for (var i = 1; i < numPowers; i++) + factors[i] = factors[i - 1] * factors[i - 1]; + } + + var result = ToBigIntegerInner(0, weightLeft + 1, digits, factors); + return sign == SignPositive ? result : -result; + + static BigInteger ToBigIntegerInner(int offset, int length, ushort[] digits, BigInteger[]? factors) + { + if (length <= 2) + { + var r = 0U; + for (var i = offset; i < offset + length; i++) + { + r *= 10000U; + r += i < digits.Length ? digits[i] : 0U; + } + return r; + } + else + { + // Split the input into two halves, the lower one should be a power of two in digit length, + // then multiply the higher part with a precomputed power of 10^8 and add the results. + var lenFirstHalf = 2 << 1; // 2 ushorts fit in one uint, skip 1 since we've already covered the base case. + var pos = 0; + while (lenFirstHalf < length) + { + lenFirstHalf <<= 1; + ++pos; + } + var factor = factors![pos]; + lenFirstHalf >>= 1; + var lo = ToBigIntegerInner(offset + length - lenFirstHalf, lenFirstHalf, digits, factors); + var hi = ToBigIntegerInner(offset, length - lenFirstHalf, digits, factors); + return hi * factor + lo; // .NET uses Karatsuba multiplication, so this will be fast. + } + } + } + + #endregion + + #region Write + + /// + public override int ValidateAndGetLength(decimal value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + { + lengthCache ??= new NpgsqlLengthCache(1); + if (lengthCache.IsPopulated) + return lengthCache.Get(); + + var groupCount = 0; + var raw = new DecimalRaw(value); + if (raw.Low != 0 || raw.Mid != 0 || raw.High != 0) + { + uint remainder = default; + var scaleChunk = raw.Scale % MaxGroupScale; + if (scaleChunk > 0) + { + var divisor = DecimalRaw.Powers10[scaleChunk]; + var multiplier = DecimalRaw.Powers10[MaxGroupScale - scaleChunk]; + remainder = DecimalRaw.Divide(ref raw, divisor) * multiplier; + } + + while (remainder == 0) + remainder = DecimalRaw.Divide(ref raw, MaxGroupSize); + + groupCount++; + + while (raw.Low != 0 || raw.Mid != 0 || raw.High != 0) + { + DecimalRaw.Divide(ref raw, MaxGroupSize); + groupCount++; + } + } + + return lengthCache.Set((4 + groupCount) * sizeof(short)); + } + + /// + public int ValidateAndGetLength(short value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLength((decimal)value, ref lengthCache, parameter); + /// + public int ValidateAndGetLength(int value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLength((decimal)value, ref lengthCache, parameter); + /// + public int ValidateAndGetLength(long value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLength((decimal)value, ref lengthCache, parameter); + /// + public int ValidateAndGetLength(float value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLength((decimal)value, ref lengthCache, parameter); + /// + public int ValidateAndGetLength(double value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLength((decimal)value, ref lengthCache, parameter); + /// + public int ValidateAndGetLength(byte value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLength((decimal)value, ref lengthCache, parameter); + + public override async Task Write(decimal value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + if (buf.WriteSpaceLeft < (4 + MaxGroupCount) * sizeof(short)) + await buf.Flush(async, cancellationToken); + + WriteInner(new DecimalRaw(value), buf); + + static void WriteInner(DecimalRaw raw, NpgsqlWriteBuffer buf) + { + var weight = 0; + var groupCount = 0; + Span groups = stackalloc short[MaxGroupCount]; + + if (raw.Low != 0 || raw.Mid != 0 || raw.High != 0) + { + var scale = raw.Scale; + weight = -scale / MaxGroupScale - 1; + + uint remainder; + var scaleChunk = scale % MaxGroupScale; + if (scaleChunk > 0) + { + var divisor = DecimalRaw.Powers10[scaleChunk]; + var multiplier = DecimalRaw.Powers10[MaxGroupScale - scaleChunk]; + remainder = DecimalRaw.Divide(ref raw, divisor) * multiplier; + + if (remainder != 0) + { + weight--; + goto WriteGroups; + } + } + + while ((remainder = DecimalRaw.Divide(ref raw, MaxGroupSize)) == 0) + weight++; + + WriteGroups: + groups[groupCount++] = (short)remainder; + + while (raw.Low != 0 || raw.Mid != 0 || raw.High != 0) + groups[groupCount++] = (short)DecimalRaw.Divide(ref raw, MaxGroupSize); + } + + buf.WriteInt16(groupCount); + buf.WriteInt16(groupCount + weight); + buf.WriteInt16(raw.Negative ? SignNegative : SignPositive); + buf.WriteInt16(raw.Scale); + + while (groupCount > 0) + buf.WriteInt16(groups[--groupCount]); + } + } + + /// + public Task Write(short value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => Write((decimal)value, buf, lengthCache, parameter, async, cancellationToken); + /// + public Task Write(int value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => Write((decimal)value, buf, lengthCache, parameter, async, cancellationToken); + /// + public Task Write(long value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => Write((decimal)value, buf, lengthCache, parameter, async, cancellationToken); + /// + public Task Write(byte value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => Write((decimal)value, buf, lengthCache, parameter, async, cancellationToken); + /// + public Task Write(float value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => Write((decimal)value, buf, lengthCache, parameter, async, cancellationToken); + /// + public Task Write(double value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => Write((decimal)value, buf, lengthCache, parameter, async, cancellationToken); + + static ushort[] FromBigInteger(BigInteger value) + { + var str = value.ToString(CultureInfo.InvariantCulture); + if (str == "0") + return new ushort[4]; + + var negative = str[0] == '-'; + var strLen = str.Length; + var numGroups = (strLen - (negative ? 1 : 0) + 3) / 4; + + if (numGroups > 131072 / 4) + throw new InvalidCastException("Cannot write a BigInteger with more than 131072 digits"); + + var result = new ushort[4 + numGroups]; + + var strPos = strLen - numGroups * 4; + + var firstDigit = 0; + for (var i = 0; i < 4; i++) + { + if (strPos >= 0 && str[strPos] != '-') + firstDigit = firstDigit * 10 + (str[strPos] - '0'); + strPos++; + } + + result[4] = (ushort)firstDigit; + + for (var i = 1; i < numGroups; i++) + { + result[4 + i] = (ushort)((((str[strPos++] - '0') * 10 + (str[strPos++] - '0')) * 10 + (str[strPos++] - '0')) * 10 + + (str[strPos++] - '0')); + + } + + var lastNonZeroDigitPos = numGroups - 1; + while (result[4 + lastNonZeroDigitPos] == 0) + lastNonZeroDigitPos--; + + result[0] = (ushort)(lastNonZeroDigitPos + 1); // number of items in array + result[1] = (ushort)(numGroups - 1); // weight + result[2] = (ushort)(negative ? SignNegative : SignPositive); + result[3] = 0; // dscale + + return result; + } + + public int ValidateAndGetLength(BigInteger value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + { + lengthCache ??= new NpgsqlLengthCache(1); + if (lengthCache.IsPopulated) + return lengthCache.Get(); + + var result = FromBigInteger(value); + if (parameter != null) + parameter.ConvertedValue = result; + + return lengthCache.Set((4 + result[0]) * sizeof(ushort)); + } + + public async Task Write(BigInteger value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, + CancellationToken cancellationToken = default) + { + var result = (ushort[])(parameter?.ConvertedValue ?? FromBigInteger(value))!; + var len = 4 + result[0]; + var pos = 0; + while (len-- > 0) + { + if (buf.WriteSpaceLeft < sizeof(ushort)) + await buf.Flush(async, cancellationToken); + buf.WriteUInt16(result[pos++]); + } + } + + #endregion +} + +partial class NumericHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + Decimal converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + Byte converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + Int16 converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + Int32 converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + Int64 converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + Single converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + Double converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + BigInteger converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type NumericHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + Decimal converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Byte converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Int16 converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Int32 converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Int64 converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Single converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Double converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + BigInteger converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type NumericHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/SingleHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/SingleHandler.cs new file mode 100644 index 0000000..05f34f4 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/SingleHandler.cs @@ -0,0 +1,74 @@ +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers.NumericHandlers; + +/// +/// A type handler for the PostgreSQL real data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-numeric.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class SingleHandler : NpgsqlSimpleTypeHandler, INpgsqlSimpleTypeHandler +{ + public SingleHandler(PostgresType pgType) : base(pgType) {} + + #region Read + + /// + public override float Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => buf.ReadSingle(); + + double INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => Read(buf, len, fieldDescription); + + #endregion Read + + #region Write + + /// + public int ValidateAndGetLength(double value, NpgsqlParameter? parameter) => 4; + /// + public override int ValidateAndGetLength(float value, NpgsqlParameter? parameter) => 4; + + /// + public void Write(double value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteSingle((float)value); + /// + public override void Write(float value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) => buf.WriteSingle(value); + + #endregion Write +} + +partial class SingleHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + Single converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + Double converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type SingleHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + Single converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Double converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type SingleHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/UInt32Handler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/UInt32Handler.cs new file mode 100644 index 0000000..cc0de2a --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/UInt32Handler.cs @@ -0,0 +1,56 @@ +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers.NumericHandlers; + +/// +/// A type handler for PostgreSQL unsigned 32-bit data types. This is only used for internal types. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-oid.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class UInt32Handler : NpgsqlSimpleTypeHandler +{ + public UInt32Handler(PostgresType pgType) : base(pgType) {} + + /// + public override uint Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => buf.ReadUInt32(); + + /// + public override int ValidateAndGetLength(uint value, NpgsqlParameter? parameter) => 4; + + /// + public override void Write(uint value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + => buf.WriteUInt32(value); +} + +partial class UInt32Handler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + UInt32 converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type UInt32Handler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + UInt32 converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type UInt32Handler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/UInt64Handler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/UInt64Handler.cs new file mode 100644 index 0000000..97283e9 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/NumericHandlers/UInt64Handler.cs @@ -0,0 +1,54 @@ +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers.NumericHandlers; + +/// +/// A type handler for PostgreSQL unsigned 64-bit data types. This is only used for internal types. +/// +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class UInt64Handler : NpgsqlSimpleTypeHandler +{ + public UInt64Handler(PostgresType pgType) : base(pgType) {} + + /// + public override ulong Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => buf.ReadUInt64(); + + /// + public override int ValidateAndGetLength(ulong value, NpgsqlParameter? parameter) => 8; + + /// + public override void Write(ulong value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + => buf.WriteUInt64(value); +} + +partial class UInt64Handler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + UInt64 converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type UInt64Handler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + UInt64 converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type UInt64Handler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/RangeHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/RangeHandler.cs new file mode 100644 index 0000000..cf68b05 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/RangeHandler.cs @@ -0,0 +1,203 @@ +using System.Diagnostics.CodeAnalysis; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using NpgsqlTypes; + +namespace Npgsql.Internal.TypeHandlers; + +/// +/// A type handler for PostgreSQL range types. +/// +/// +/// See https://www.postgresql.org/docs/current/static/rangetypes.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +/// The range subtype. +// NOTE: This cannot inherit from NpgsqlTypeHandler>, since that triggers infinite generic recursion in Native AOT +public partial class RangeHandler : NpgsqlTypeHandler, INpgsqlTypeHandler> +{ + /// + /// The type handler for the subtype that this range type holds + /// + protected NpgsqlTypeHandler SubtypeHandler { get; } + + /// + public RangeHandler(PostgresType rangePostgresType, NpgsqlTypeHandler subtypeHandler) + : base(rangePostgresType) + => SubtypeHandler = subtypeHandler; + + public override Type GetFieldType(FieldDescription? fieldDescription = null) => typeof(NpgsqlRange); + public override Type GetProviderSpecificFieldType(FieldDescription? fieldDescription = null) => typeof(NpgsqlRange); + + /// + public override NpgsqlTypeHandler CreateArrayHandler(PostgresArrayType pgArrayType, ArrayNullabilityMode arrayNullabilityMode) + => new ArrayHandler>(pgArrayType, this, arrayNullabilityMode); + + /// + public override NpgsqlTypeHandler CreateRangeHandler(PostgresType pgRangeType) + => throw new NotSupportedException(); + + /// + public override NpgsqlTypeHandler CreateMultirangeHandler(PostgresMultirangeType pgMultirangeType) + => throw new NotSupportedException(); + + #region Read + + /// + public ValueTask> Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + => ReadRange(buf, len, async, fieldDescription); + + protected internal async ValueTask> ReadRange(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + { + await buf.Ensure(1, async); + + var flags = (RangeFlags)buf.ReadByte(); + if ((flags & RangeFlags.Empty) != 0) + return NpgsqlRange.Empty; + + var lowerBound = default(TAnySubtype); + var upperBound = default(TAnySubtype); + + if ((flags & RangeFlags.LowerBoundInfinite) == 0) + lowerBound = await SubtypeHandler.ReadWithLength(buf, async); + + if ((flags & RangeFlags.UpperBoundInfinite) == 0) + upperBound = await SubtypeHandler.ReadWithLength(buf, async); + + return new NpgsqlRange(lowerBound, upperBound, flags); + } + + public override async ValueTask ReadAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + => await Read(buf, len, async, fieldDescription); + + #endregion + + #region Write + + /// + public int ValidateAndGetLength(NpgsqlRange value, [NotNullIfNotNull("lengthCache")] ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLengthRange(value, ref lengthCache, parameter); + + protected internal int ValidateAndGetLengthRange(NpgsqlRange value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + { + var totalLen = 1; + var lengthCachePos = lengthCache?.Position ?? 0; + if (!value.IsEmpty) + { + if (!value.LowerBoundInfinite) + { + totalLen += 4; + if (value.LowerBound is not null) + totalLen += SubtypeHandler.ValidateAndGetLength(value.LowerBound, ref lengthCache, null); + } + + if (!value.UpperBoundInfinite) + { + totalLen += 4; + if (value.UpperBound is not null) + totalLen += SubtypeHandler.ValidateAndGetLength(value.UpperBound, ref lengthCache, null); + } + } + + // If we're traversing an already-populated length cache, rewind to first element slot so that + // the elements' handlers can access their length cache values + if (lengthCache != null && lengthCache.IsPopulated) + lengthCache.Position = lengthCachePos; + + return totalLen; + } + + /// + public Task Write(NpgsqlRange value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => WriteRange(value, buf, lengthCache, parameter, async, cancellationToken); + + protected internal async Task WriteRange(NpgsqlRange value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + if (buf.WriteSpaceLeft < 1) + await buf.Flush(async, cancellationToken); + + buf.WriteByte((byte)value.Flags); + + if (value.IsEmpty) + return; + + if (!value.LowerBoundInfinite) + await SubtypeHandler.WriteWithLength(value.LowerBound, buf, lengthCache, null, async, cancellationToken); + + if (!value.UpperBoundInfinite) + await SubtypeHandler.WriteWithLength(value.UpperBound, buf, lengthCache, null, async, cancellationToken); + } + + #endregion + + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + NpgsqlRange converted => ((INpgsqlTypeHandler>)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type RangeHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + NpgsqlRange converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type RangeHandler") + }; +} + +/// +/// Type handler for PostgreSQL range types. +/// +/// +/// Introduced in PostgreSQL 9.2. +/// https://www.postgresql.org/docs/current/static/rangetypes.html +/// +/// The main range subtype. +/// An alternative range subtype. +public class RangeHandler : RangeHandler, INpgsqlTypeHandler> +{ + /// + public RangeHandler(PostgresType rangePostgresType, NpgsqlTypeHandler subtypeHandler) + : base(rangePostgresType, subtypeHandler) { } + + ValueTask> INpgsqlTypeHandler>.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + => ReadRange(buf, len, async, fieldDescription); + + /// + public int ValidateAndGetLength(NpgsqlRange value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLengthRange(value, ref lengthCache, parameter); + + /// + public Task Write(NpgsqlRange value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => WriteRange(value, buf, lengthCache, parameter, async, cancellationToken); + + public override int ValidateObjectAndGetLength(object? value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + NpgsqlRange converted => ((INpgsqlTypeHandler>)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + NpgsqlRange converted => ((INpgsqlTypeHandler>)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + DBNull => 0, + null => 0, + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type RangeHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + NpgsqlRange converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + NpgsqlRange converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type RangeHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/RecordHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/RecordHandler.cs new file mode 100644 index 0000000..871379b --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/RecordHandler.cs @@ -0,0 +1,88 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using Npgsql.TypeMapping; + +namespace Npgsql.Internal.TypeHandlers; + +/// +/// Type handler for PostgreSQL record types. +/// +/// +/// https://www.postgresql.org/docs/current/static/datatype-pseudo.html +/// +/// Encoding (identical to composite): +/// A 32-bit integer with the number of columns, then for each column: +/// * An OID indicating the type of the column +/// * The length of the column(32-bit integer), or -1 if null +/// * The column data encoded as binary +/// +partial class RecordHandler : NpgsqlTypeHandler +{ + readonly ConnectorTypeMapper _typeMapper; + + public RecordHandler(PostgresType postgresType, ConnectorTypeMapper typeMapper) + : base(postgresType) + => _typeMapper = typeMapper; + + #region Read + + public override async ValueTask Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + { + await buf.Ensure(4, async); + var fieldCount = buf.ReadInt32(); + var result = new object[fieldCount]; + + for (var i = 0; i < fieldCount; i++) + { + await buf.Ensure(8, async); + var typeOID = buf.ReadUInt32(); + var fieldLen = buf.ReadInt32(); + if (fieldLen == -1) // Null field, simply skip it and leave at default + continue; + result[i] = await _typeMapper.ResolveByOID(typeOID).ReadAsObject(buf, fieldLen, async); + } + + return result; + } + + #endregion + + #region Write (unsupported) + + public override int ValidateAndGetLength(object[] value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => throw new NotSupportedException("Can't write record types"); + + public override Task Write(object[] value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => throw new NotSupportedException("Can't write record types"); + + #endregion +} + +partial class RecordHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + Object[] converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type RecordHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + Object[] converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type RecordHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/TextHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/TextHandler.cs new file mode 100644 index 0000000..1790eb5 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/TextHandler.cs @@ -0,0 +1,320 @@ +using System; +using System.Data; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers; + +/// +/// A type handler for PostgreSQL character data types (text, char, varchar, xml...). +/// +/// +/// See https://www.postgresql.org/docs/current/datatype-character.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class TextHandler : NpgsqlTypeHandler, INpgsqlTypeHandler, INpgsqlTypeHandler>, + INpgsqlTypeHandler, INpgsqlTypeHandler, ITextReaderHandler +{ + // Text types are handled a bit more efficiently when sent as text than as binary + // see https://github.com/npgsql/npgsql/issues/1210#issuecomment-235641670 + internal override bool PreferTextWrite => true; + + readonly Encoding _encoding; + + #region State + + readonly char[] _singleCharArray = new char[1]; + + #endregion + + /// + protected internal TextHandler(PostgresType postgresType, Encoding encoding) + : base(postgresType) + => _encoding = encoding; + + #region Read + + /// + public override ValueTask Read(NpgsqlReadBuffer buf, int byteLen, bool async, FieldDescription? fieldDescription = null) + { + return buf.ReadBytesLeft >= byteLen + ? new ValueTask(buf.ReadString(byteLen)) + : ReadLong(buf, byteLen, async); + + static async ValueTask ReadLong(NpgsqlReadBuffer buf, int byteLen, bool async) + { + if (byteLen <= buf.Size) + { + // The string's byte representation can fit in our read buffer, read it. + while (buf.ReadBytesLeft < byteLen) + await buf.ReadMore(async); + return buf.ReadString(byteLen); + } + + // Bad case: the string's byte representation doesn't fit in our buffer. + // This is rare - will only happen in CommandBehavior.Sequential mode (otherwise the + // entire row is in memory). Tweaking the buffer length via the connection string can + // help avoid this. + + // Allocate a temporary byte buffer to hold the entire string and read it in chunks. + var tempBuf = new byte[byteLen]; + var pos = 0; + while (true) + { + var len = Math.Min(buf.ReadBytesLeft, byteLen - pos); + buf.ReadBytes(tempBuf, pos, len); + pos += len; + if (pos < byteLen) + { + await buf.ReadMore(async); + continue; + } + break; + } + return buf.TextEncoding.GetString(tempBuf); + } + } + + async ValueTask INpgsqlTypeHandler.Read(NpgsqlReadBuffer buf, int byteLen, bool async, FieldDescription? fieldDescription) + { + if (byteLen <= buf.Size) + { + // The string's byte representation can fit in our read buffer, read it. + while (buf.ReadBytesLeft < byteLen) + await buf.ReadMore(async); + return buf.ReadChars(byteLen); + } + + // TODO: The following can be optimized with Decoder - no need to allocate a byte[] + var tempBuf = new byte[byteLen]; + var pos = 0; + while (true) + { + var len = Math.Min(buf.ReadBytesLeft, byteLen - pos); + buf.ReadBytes(tempBuf, pos, len); + pos += len; + if (pos < byteLen) + { + await buf.ReadMore(async); + continue; + } + break; + } + return buf.TextEncoding.GetChars(tempBuf); + } + + async ValueTask INpgsqlTypeHandler.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + { + // Make sure we have enough bytes in the buffer for a single character + // We can get here a much bigger length in case it's a string + // while we want to read only its first character + var maxBytes = Math.Min(buf.TextEncoding.GetMaxByteCount(1), len); + await buf.Ensure(maxBytes, async); + + var decoder = buf.TextEncoding.GetDecoder(); + decoder.Convert(buf.Buffer, buf.ReadPosition, maxBytes, _singleCharArray, 0, 1, true, out var bytesUsed, out var charsUsed, out _); + // We've been requested to read 'len' bytes, which is why we're going to skip them + // This is important for NpgsqlDataReader with CommandBehavior.SequentialAccess + // which tracks how many bytes it has to skip for the next column + await buf.Skip(len, async); + + if (charsUsed < 1) + throw new NpgsqlException("Could not read char - string was empty"); + + return _singleCharArray[0]; + } + + ValueTask> INpgsqlTypeHandler>.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + => throw new NotSupportedException("Only writing ArraySegment to PostgreSQL text is supported, no reading."); + + ValueTask INpgsqlTypeHandler.Read(NpgsqlReadBuffer buf, int byteLen, bool async, FieldDescription? fieldDescription) + { + var bytes = new byte[byteLen]; + if (buf.ReadBytesLeft >= byteLen) + { + buf.ReadBytes(bytes, 0, byteLen); + return new ValueTask(bytes); + } + return ReadLong(buf, bytes, byteLen, async); + + static async ValueTask ReadLong(NpgsqlReadBuffer buf, byte[] bytes, int byteLen, bool async) + { + if (byteLen <= buf.Size) + { + // The bytes can fit in our read buffer, read it. + while (buf.ReadBytesLeft < byteLen) + await buf.ReadMore(async); + buf.ReadBytes(bytes, 0, byteLen); + return bytes; + } + + // Bad case: the bytes don't fit in our buffer. + // This is rare - will only happen in CommandBehavior.Sequential mode (otherwise the + // entire row is in memory). Tweaking the buffer length via the connection string can + // help avoid this. + + var pos = 0; + while (true) + { + var len = Math.Min(buf.ReadBytesLeft, byteLen - pos); + buf.ReadBytes(bytes, pos, len); + pos += len; + if (pos < byteLen) + { + await buf.ReadMore(async); + continue; + } + break; + } + return bytes; + } + } + + #endregion + + #region Write + + /// + public override unsafe int ValidateAndGetLength(string value, [NotNullIfNotNull("lengthCache")] ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + { + lengthCache ??= new NpgsqlLengthCache(1); + if (lengthCache.IsPopulated) + return lengthCache.Get(); + + if (parameter == null || parameter.Size <= 0 || parameter.Size >= value.Length) + return lengthCache.Set(_encoding.GetByteCount(value)); + fixed (char* p = value) + return lengthCache.Set(_encoding.GetByteCount(p, parameter.Size)); + } + + /// + public virtual int ValidateAndGetLength(char[] value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + { + lengthCache ??= new NpgsqlLengthCache(1); + if (lengthCache.IsPopulated) + return lengthCache.Get(); + + return lengthCache.Set( + parameter == null || parameter.Size <= 0 || parameter.Size >= value.Length + ? _encoding.GetByteCount(value) + : _encoding.GetByteCount(value, 0, parameter.Size) + ); + } + + /// + public virtual int ValidateAndGetLength(ArraySegment value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + { + lengthCache ??= new NpgsqlLengthCache(1); + if (lengthCache.IsPopulated) + return lengthCache.Get(); + + if (parameter?.Size > 0) + throw new ArgumentException($"Parameter {parameter.ParameterName} is of type ArraySegment and should not have its Size set", parameter.ParameterName); + + return lengthCache.Set(value.Array is null ? 0 : _encoding.GetByteCount(value.Array, value.Offset, value.Count)); + } + + /// + public int ValidateAndGetLength(char value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + { + _singleCharArray[0] = value; + return _encoding.GetByteCount(_singleCharArray); + } + + /// + public int ValidateAndGetLength(byte[] value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value.Length; + + /// + public override Task Write(string value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => WriteString(value, buf, lengthCache!, parameter, async, cancellationToken); + + /// + public virtual Task Write(char[] value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + var charLen = parameter == null || parameter.Size <= 0 || parameter.Size >= value.Length + ? value.Length + : parameter.Size; + return buf.WriteChars(value, 0, charLen, lengthCache!.GetLast(), async, cancellationToken); + } + + /// + public virtual Task Write(ArraySegment value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value.Array is null ? Task.CompletedTask : buf.WriteChars(value.Array, value.Offset, value.Count, lengthCache!.GetLast(), async, cancellationToken); + + Task WriteString(string str, NpgsqlWriteBuffer buf, NpgsqlLengthCache lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + var charLen = parameter == null || parameter.Size <= 0 || parameter.Size >= str.Length + ? str.Length + : parameter.Size; + return buf.WriteString(str, charLen, lengthCache.GetLast(), async, cancellationToken); + } + + /// + public Task Write(char value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + _singleCharArray[0] = value; + var len = _encoding.GetByteCount(_singleCharArray); + return buf.WriteChars(_singleCharArray, 0, 1, len, async, cancellationToken); + } + + /// + public Task Write(byte[] value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => buf.WriteBytesRaw(value, async, cancellationToken); + + #endregion + + /// + public virtual TextReader GetTextReader(Stream stream) => new StreamReader(stream, _encoding); +} + +partial class TextHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + String converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + Char[] converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + ArraySegment converted => ((INpgsqlTypeHandler>)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + Char converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + Byte[] converted => ((INpgsqlTypeHandler)this).ValidateAndGetLength(converted, ref lengthCache, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type TextHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + String converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Char[] converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + ArraySegment converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Char converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + Byte[] converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type TextHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/UnknownTypeHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/UnknownTypeHandler.cs new file mode 100644 index 0000000..2682014 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/UnknownTypeHandler.cs @@ -0,0 +1,95 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers; + +/// +/// Handles "conversions" for columns sent by the database with unknown OIDs. +/// This differs from TextHandler in that its a text-only handler (we don't want to receive binary +/// representations of the types registered here). +/// Note that this handler is also used in the very initial query that loads the OID mappings +/// (chicken and egg problem). +/// Also used for sending parameters with unknown types (OID=0) +/// +class UnknownTypeHandler : TextHandler +{ + readonly NpgsqlConnector _connector; + + internal UnknownTypeHandler(NpgsqlConnector connector) + : base(UnknownBackendType.Instance, connector.TextEncoding) + => _connector = connector; + + #region Read + + public override ValueTask Read(NpgsqlReadBuffer buf, int byteLen, bool async, FieldDescription? fieldDescription = null) + { + if (fieldDescription == null) + throw new Exception($"Received an unknown field but {nameof(fieldDescription)} is null (i.e. COPY mode)"); + + if (fieldDescription.IsBinaryFormat) + { + // At least get the name of the PostgreSQL type for the exception + throw new NotSupportedException( + _connector.TypeMapper.DatabaseInfo.ByOID.TryGetValue(fieldDescription.TypeOID, out var pgType) + ? $"The field '{fieldDescription.Name}' has type '{pgType.DisplayName}', which is currently unknown to Npgsql. You can retrieve it as a string by marking it as unknown, please see the FAQ." + : $"The field '{fieldDescription.Name}' has a type currently unknown to Npgsql (OID {fieldDescription.TypeOID}). You can retrieve it as a string by marking it as unknown, please see the FAQ." + ); + } + + return base.Read(buf, byteLen, async, fieldDescription); + } + + #endregion Read + + #region Write + + // Allow writing anything that is a string or can be converted to one via the unknown type handler + + protected internal override int ValidateAndGetLengthCustom( + [DisallowNull] TAny value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateObjectAndGetLength(value, ref lengthCache, parameter); + + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + { + if (value is string asString) + return base.ValidateAndGetLength(asString, ref lengthCache, parameter); + + if (parameter == null) + throw CreateConversionButNoParamException(value.GetType()); + + var converted = Convert.ToString(value)!; + parameter.ConvertedValue = converted; + + return base.ValidateAndGetLength(converted, ref lengthCache, parameter); + } + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + if (value is null or DBNull) + return base.WriteObjectWithLength(value, buf, lengthCache, parameter, async, cancellationToken); + + var convertedValue = value is string asString + ? asString + : (string)parameter!.ConvertedValue!; + + if (buf.WriteSpaceLeft < 4) + return WriteWithLengthLong(value, convertedValue, buf, lengthCache, parameter, async, cancellationToken); + + buf.WriteInt32(ValidateObjectAndGetLength(value, ref lengthCache, parameter)); + return base.Write(convertedValue, buf, lengthCache, parameter, async, cancellationToken); + + async Task WriteWithLengthLong(object value, string convertedValue, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken) + { + await buf.Flush(async, cancellationToken); + buf.WriteInt32(ValidateObjectAndGetLength(value!, ref lengthCache, parameter)); + await base.Write(convertedValue, buf, lengthCache, parameter, async, cancellationToken); + } + } + + #endregion Write +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/UnmappedEnumHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/UnmappedEnumHandler.cs new file mode 100644 index 0000000..b559fd1 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/UnmappedEnumHandler.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using NpgsqlTypes; + +namespace Npgsql.Internal.TypeHandlers; + +class UnmappedEnumHandler : TextHandler +{ + readonly INpgsqlNameTranslator _nameTranslator; + + readonly Dictionary _enumToLabel = new(); + readonly Dictionary _labelToEnum = new(); + + Type? _resolvedType; + + internal UnmappedEnumHandler(PostgresEnumType pgType, INpgsqlNameTranslator nameTranslator, Encoding encoding) + : base(pgType, encoding) + => _nameTranslator = nameTranslator; + + #region Read + + protected internal override async ValueTask ReadCustom(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + { + var s = await base.Read(buf, len, async, fieldDescription); + if (typeof(TAny) == typeof(string)) + return (TAny)(object)s; + + if (_resolvedType != typeof(TAny)) + Map(typeof(TAny)); + + if (!_labelToEnum.TryGetValue(s, out var value)) + throw new InvalidCastException($"Received enum value '{s}' from database which wasn't found on enum {typeof(TAny)}"); + + // TODO: Avoid boxing + return (TAny)(object)value; + } + + public override ValueTask Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + => base.Read(buf, len, async, fieldDescription); + + #endregion + + #region Write + + public override int ValidateObjectAndGetLength(object? value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value is null || value is DBNull + ? 0 + : ValidateAndGetLength(value, ref lengthCache, parameter); + + protected internal override int ValidateAndGetLengthCustom(TAny value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => ValidateAndGetLength(value!, ref lengthCache, parameter); + + [UnconditionalSuppressMessage("Unmapped enums currently aren't trimming-safe.", "IL2072")] + int ValidateAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + { + var type = value.GetType(); + if (type == typeof(string)) + return base.ValidateAndGetLength((string)value, ref lengthCache, parameter); + if (_resolvedType != type) + Map(type); + + // TODO: Avoid boxing + return _enumToLabel.TryGetValue((Enum)value, out var str) + ? base.ValidateAndGetLength(str, ref lengthCache, parameter) + : throw new InvalidCastException($"Can't write value {value} as enum {type}"); + } + + // TODO: This boxes the enum (again) + protected override Task WriteWithLengthCustom(TAny value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken) + => WriteObjectWithLength(value!, buf, lengthCache, parameter, async, cancellationToken); + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + if (value is null || value is DBNull) + return WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken); + + if (buf.WriteSpaceLeft < 4) + return WriteWithLengthLong(value, buf, lengthCache, parameter, async, cancellationToken); + + buf.WriteInt32(ValidateAndGetLength(value, ref lengthCache, parameter)); + return Write(value, buf, lengthCache, parameter, async, cancellationToken); + + async Task WriteWithLengthLong(object value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken) + { + await buf.Flush(async, cancellationToken); + buf.WriteInt32(ValidateAndGetLength(value, ref lengthCache, parameter)); + await Write(value, buf, lengthCache, parameter, async, cancellationToken); + } + } + + [UnconditionalSuppressMessage("Unmapped enums currently aren't trimming-safe.", "IL2072")] + internal Task Write(object value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + var type = value.GetType(); + if (type == typeof(string)) + return base.Write((string)value, buf, lengthCache, parameter, async, cancellationToken); + if (_resolvedType != type) + Map(type); + + // TODO: Avoid boxing + if (!_enumToLabel.TryGetValue((Enum)value, out var str)) + throw new InvalidCastException($"Can't write value {value} as enum {type}"); + return base.Write(str, buf, lengthCache, parameter, async, cancellationToken); + } + + #endregion + + #region Misc + + void Map([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] Type type) + { + Debug.Assert(_resolvedType != type); + + _enumToLabel.Clear(); + _labelToEnum.Clear(); + + foreach (var field in type.GetFields(BindingFlags.Static | BindingFlags.Public)) + { + var attribute = (PgNameAttribute?)field.GetCustomAttributes(typeof(PgNameAttribute), false).FirstOrDefault(); + var enumName = attribute?.PgName ?? _nameTranslator.TranslateMemberName(field.Name); + var enumValue = (Enum)field.GetValue(null)!; + + _enumToLabel[enumValue] = enumName; + _labelToEnum[enumName] = enumValue; + } + + _resolvedType = type; + } + + #endregion +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/UuidHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/UuidHandler.cs new file mode 100644 index 0000000..e1bc448 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/UuidHandler.cs @@ -0,0 +1,101 @@ +using System; +using System.Runtime.InteropServices; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers; + +/// +/// A type handler for the PostgreSQL uuid data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-uuid.html. +/// +/// The type handler API allows customizing Npgsql's behavior in powerful ways. However, although it is public, it +/// should be considered somewhat unstable, and may change in breaking ways, including in non-major releases. +/// Use it at your own risk. +/// +public partial class UuidHandler : NpgsqlSimpleTypeHandler +{ + // The following table shows .NET GUID vs Postgres UUID (RFC 4122) layouts. + // + // Note that the first fields are converted from/to native endianness (handled by the Read* + // and Write* methods), while the last field is always read/written in big-endian format. + // + // We're passing BitConverter.IsLittleEndian to prevent reversing endianness on little-endian systems. + // + // | Bits | Bytes | Name | Endianness (GUID) | Endianness (RFC 4122) | + // | ---- | ----- | ----- | ----------------- | --------------------- | + // | 32 | 4 | Data1 | Native | Big | + // | 16 | 2 | Data2 | Native | Big | + // | 16 | 2 | Data3 | Native | Big | + // | 64 | 8 | Data4 | Big | Big | + + public UuidHandler(PostgresType pgType) : base(pgType) {} + + /// + public override Guid Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + { + var raw = new GuidRaw + { + Data1 = buf.ReadInt32(), + Data2 = buf.ReadInt16(), + Data3 = buf.ReadInt16(), + Data4 = buf.ReadInt64(BitConverter.IsLittleEndian) + }; + + return raw.Value; + } + + /// + public override int ValidateAndGetLength(Guid value, NpgsqlParameter? parameter) + => 16; + + /// + public override void Write(Guid value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + { + var raw = new GuidRaw(value); + + buf.WriteInt32(raw.Data1); + buf.WriteInt16(raw.Data2); + buf.WriteInt16(raw.Data3); + buf.WriteInt64(raw.Data4, BitConverter.IsLittleEndian); + } + + [StructLayout(LayoutKind.Explicit)] + struct GuidRaw + { + [FieldOffset(00)] public Guid Value; + [FieldOffset(00)] public int Data1; + [FieldOffset(04)] public short Data2; + [FieldOffset(06)] public short Data3; + [FieldOffset(08)] public long Data4; + public GuidRaw(Guid value) : this() => Value = value; + } +} + +partial class UuidHandler +{ + public override int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + + Guid converted => ((INpgsqlSimpleTypeHandler)this).ValidateAndGetLength(converted, parameter), + + + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type UuidHandler") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + + Guid converted => WriteWithLength(converted, buf, lengthCache, parameter, async, cancellationToken), + + + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type UuidHandler") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandlers/VoidHandler.cs b/LibExternal/Npgsql/Internal/TypeHandlers/VoidHandler.cs new file mode 100644 index 0000000..55fbf89 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandlers/VoidHandler.cs @@ -0,0 +1,41 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandlers; + +/// +/// https://www.postgresql.org/docs/current/static/datatype-boolean.html +/// +class VoidHandler : NpgsqlSimpleTypeHandler +{ + public VoidHandler(PostgresType pgType) : base(pgType) {} + + public override DBNull Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => DBNull.Value; + + public override int ValidateAndGetLength(DBNull value, NpgsqlParameter? parameter) + => throw new NotSupportedException(); + + public override void Write(DBNull value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter) + => throw new NotSupportedException(); + + public override int ValidateObjectAndGetLength(object? value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => value switch + { + DBNull => 0, + null => 0, + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type {nameof(VoidHandler)}") + }; + + public override Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => value switch + { + DBNull => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + null => WriteWithLength(DBNull.Value, buf, lengthCache, parameter, async, cancellationToken), + _ => throw new InvalidCastException($"Can't write CLR type {value.GetType()} with handler type {nameof(VoidHandler)}") + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandling/INpgsqlSimpleTypeHandler.cs b/LibExternal/Npgsql/Internal/TypeHandling/INpgsqlSimpleTypeHandler.cs new file mode 100644 index 0000000..9a8ebf8 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandling/INpgsqlSimpleTypeHandler.cs @@ -0,0 +1,47 @@ +using System.Diagnostics.CodeAnalysis; +using Npgsql.BackendMessages; + +namespace Npgsql.Internal.TypeHandling; + +/// +/// Type handlers that wish to support reading other types in additional to the main one can +/// implement this interface for all those types. +/// +public interface INpgsqlSimpleTypeHandler +{ + /// + /// Reads a value of type with the given length from the provided buffer, + /// with the assumption that it is entirely present in the provided memory buffer and no I/O will be + /// required. + /// + /// The buffer from which to read. + /// The byte length of the value. The buffer might not contain the full length, requiring I/O to be performed. + /// Additional PostgreSQL information about the type, such as the length in varchar(30). + /// The fully-read value. + T Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null); + + /// + /// Responsible for validating that a value represents a value of the correct and which can be + /// written for PostgreSQL - if the value cannot be written for any reason, an exception should be thrown. + /// Also returns the byte length needed to write the value. + /// + /// The value to be written to PostgreSQL + /// + /// The instance where this value resides. Can be used to access additional + /// information relevant to the write process (e.g. ). + /// + /// The number of bytes required to write the value. + int ValidateAndGetLength([DisallowNull] T value, NpgsqlParameter? parameter); + + /// + /// Writes a value to the provided buffer, with the assumption that there is enough space in the buffer + /// (no I/O will occur). The Npgsql core will have taken care of that. + /// + /// The value to write. + /// The buffer to which to write. + /// + /// The instance where this value resides. Can be used to access additional + /// information relevant to the write process (e.g. ). + /// + void Write([DisallowNull] T value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandling/INpgsqlTypeHandler.cs b/LibExternal/Npgsql/Internal/TypeHandling/INpgsqlTypeHandler.cs new file mode 100644 index 0000000..939b7f9 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandling/INpgsqlTypeHandler.cs @@ -0,0 +1,56 @@ +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; + +namespace Npgsql.Internal.TypeHandling; + +/// +/// Type handlers that wish to support reading other types in additional to the main one can +/// implement this interface for all those types. +/// +public interface INpgsqlTypeHandler +{ + /// + /// Reads a value of type with the given length from the provided buffer, + /// using either sync or async I/O. + /// + /// The buffer from which to read. + /// The byte length of the value. The buffer might not contain the full length, requiring I/O to be performed. + /// If I/O is required to read the full length of the value, whether it should be performed synchronously or asynchronously. + /// Additional PostgreSQL information about the type, such as the length in varchar(30). + /// The fully-read value. + ValueTask Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null); + + /// + /// Responsible for validating that a value represents a value of the correct and which can be + /// written for PostgreSQL - if the value cannot be written for any reason, an exception should be thrown. + /// Also returns the byte length needed to write the value. + /// + /// The value to be written to PostgreSQL + /// A cache where the length calculated during the validation phase can be stored for use at the writing phase. + /// + /// The instance where this value resides. Can be used to access additional + /// information relevant to the write process (e.g. ). + /// + /// The number of bytes required to write the value. + int ValidateAndGetLength([DisallowNull] T value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter); + + /// + /// Writes a value to the provided buffer. + /// + /// The value to write. + /// The buffer to which to write. + /// A cache where the length calculated during the validation phase can be stored for use at the writing phase. + /// + /// The instance where this value resides. Can be used to access additional + /// information relevant to the write process (e.g. ). + /// + /// + /// If I/O will be necessary (i.e. the buffer is full), determines whether it will be done synchronously or asynchronously. + /// + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + Task Write([DisallowNull] T value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandling/ITextReaderHandler.cs b/LibExternal/Npgsql/Internal/TypeHandling/ITextReaderHandler.cs new file mode 100644 index 0000000..c0c970b --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandling/ITextReaderHandler.cs @@ -0,0 +1,15 @@ +using System.Data.Common; +using System.IO; + +namespace Npgsql.Internal.TypeHandling; + +/// +/// Implemented by handlers which support , returns a standard +/// TextReader given a binary Stream. +/// +interface ITextReaderHandler +{ + TextReader GetTextReader(Stream stream); +} + +#pragma warning disable CA1032 \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandling/NpgsqlLengthCache.cs b/LibExternal/Npgsql/Internal/TypeHandling/NpgsqlLengthCache.cs new file mode 100644 index 0000000..246b407 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandling/NpgsqlLengthCache.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace Npgsql.Internal.TypeHandling; + +/// +/// An array of cached lengths for the parameters sending process. +/// +/// When sending parameters, lengths need to be calculated more than once (once for Bind, once for +/// an array, once for the string within that array). This cache optimized that. Lengths are added +/// to the cache, and then retrieved at the same order. +/// +public sealed class NpgsqlLengthCache +{ + public bool IsPopulated; + public int Position; + public List Lengths; + + public NpgsqlLengthCache() => Lengths = new List(); + + public NpgsqlLengthCache(int capacity) => Lengths = new List(capacity); + + /// + /// Stores a length value in the cache, to be fetched later via . + /// Called at the phase. + /// + /// The length parameter. + public int Set(int len) + { + Debug.Assert(!IsPopulated); + Lengths.Add(len); + Position++; + return len; + } + + /// + /// Retrieves a length value previously stored in the cache via . + /// Called at the writing phase, after validation has already occurred and the length cache is populated. + /// + /// + public int Get() + { + Debug.Assert(IsPopulated); + return Lengths[Position++]; + } + + internal int GetLast() + { + Debug.Assert(IsPopulated); + return Lengths[Position-1]; + } + + internal void Rewind() + { + Position = 0; + IsPopulated = true; + } + + internal void Clear() + { + Lengths.Clear(); + Position = 0; + IsPopulated = false; + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandling/NpgsqlSimpleTypeHandler.cs b/LibExternal/Npgsql/Internal/TypeHandling/NpgsqlSimpleTypeHandler.cs new file mode 100644 index 0000000..8475717 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandling/NpgsqlSimpleTypeHandler.cs @@ -0,0 +1,78 @@ +using System; +using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandling; + +/// +/// Base class for all simple type handlers, which read and write short, non-arbitrary lengthed +/// values to PostgreSQL. Provides a simpler API to implement when compared to - +/// Npgsql takes care of all I/O before calling into this type, so no I/O needs to be performed by it. +/// +/// +/// The default CLR type that this handler will read and write. For example, calling +/// on a column with this handler will return a value with type . +/// Type handlers can support additional types by implementing . +/// +public abstract class NpgsqlSimpleTypeHandler : NpgsqlTypeHandler, INpgsqlSimpleTypeHandler +{ + protected NpgsqlSimpleTypeHandler(PostgresType postgresType) : base(postgresType) {} + + /// + /// Reads a value of type with the given length from the provided buffer, + /// with the assumption that it is entirely present in the provided memory buffer and no I/O will be + /// required. + /// + /// The buffer from which to read. + /// The byte length of the value. The buffer might not contain the full length, requiring I/O to be performed. + /// Additional PostgreSQL information about the type, such as the length in varchar(30). + /// The fully-read value. + public abstract TDefault Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null); + + public sealed override ValueTask Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + => throw new NotSupportedException(); + + #region Write + + /// + /// Responsible for validating that a value represents a value of the correct and which can be + /// written for PostgreSQL - if the value cannot be written for any reason, an exception shold be thrown. + /// Also returns the byte length needed to write the value. + /// + /// The value to be written to PostgreSQL + /// + /// The instance where this value resides. Can be used to access additional + /// information relevant to the write process (e.g. ). + /// + /// The number of bytes required to write the value. + public abstract int ValidateAndGetLength(TDefault value, NpgsqlParameter? parameter); + + /// + /// Writes a value to the provided buffer, with the assumption that there is enough space in the buffer + /// (no I/O will occur). The Npgsql core will have taken care of that. + /// + /// The value to write. + /// The buffer to which to write. + /// + /// The instance where this value resides. Can be used to access additional + /// information relevant to the write process (e.g. ). + /// + public abstract void Write(TDefault value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter); + + /// + /// Simple type handlers override instead of this. + /// + public sealed override Task Write(TDefault value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + => throw new NotSupportedException(); + + /// + /// Simple type handlers override instead of this. + /// + public sealed override int ValidateAndGetLength(TDefault value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + => throw new NotSupportedException(); + + #endregion +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandling/NpgsqlSimpleTypeHandlerWithPsv.cs b/LibExternal/Npgsql/Internal/TypeHandling/NpgsqlSimpleTypeHandlerWithPsv.cs new file mode 100644 index 0000000..5fbe6d6 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandling/NpgsqlSimpleTypeHandlerWithPsv.cs @@ -0,0 +1,103 @@ +using System; +using System.Data.Common; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandlers; +using Npgsql.PostgresTypes; +using NpgsqlTypes; + +namespace Npgsql.Internal.TypeHandling; + +/// +/// A simple type handler that supports a provider-specific value in addition to its default value. +/// This is necessary mainly in cases where the CLR type cannot represent the full range of the +/// PostgreSQL type, and a custom CLR type is needed (e.g. and +/// ). The provider-specific type will be returned +/// from calls to . +/// +/// +/// The default CLR type that this handler will read and write. For example, calling +/// on a column with this handler will return a value with type . +/// Type handlers can support additional types by implementing . +/// +/// The provider-specific CLR type that this handler will read and write. +public abstract class NpgsqlSimpleTypeHandlerWithPsv : NpgsqlSimpleTypeHandler, INpgsqlSimpleTypeHandler +{ + public NpgsqlSimpleTypeHandlerWithPsv(PostgresType pgType) : base(pgType) {} + + #region Read + + /// + /// Reads a value of type with the given length from the provided buffer, + /// with the assumption that it is entirely present in the provided memory buffer and no I/O will be + /// required. + /// + /// The buffer from which to read. + /// The byte length of the value. The buffer might not contain the full length, requiring I/O to be performed. + /// Additional PostgreSQL information about the type, such as the length in varchar(30). + /// The fully-read value. + protected abstract TPsv ReadPsv(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null); + + TPsv INpgsqlSimpleTypeHandler.Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription) + => ReadPsv(buf, len, fieldDescription); + + // Since TAny isn't constrained to class? or struct (C# doesn't have a non-nullable constraint that doesn't limit us to either struct or class), + // we must use the bang operator here to tell the compiler that a null value will never returned. + + /// + /// Reads a column as the type handler's provider-specific type, assuming that it is already entirely + /// in memory (i.e. no I/O is necessary). Called by in non-sequential mode, which + /// buffers entire rows in memory. + /// + internal override object ReadPsvAsObject(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + => Read(buf, len, fieldDescription)!; + + /// + /// Reads a column as the type handler's provider-specific type. If it is not already entirely in + /// memory, sync or async I/O will be performed as specified by . + /// + internal override async ValueTask ReadPsvAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + => (await Read(buf, len, async, fieldDescription))!; + + #endregion Read + + #region Write + + /// + /// Responsible for validating that a value represents a value of the correct and which can be + /// written for PostgreSQL - if the value cannot be written for any reason, an exception shold be thrown. + /// Also returns the byte length needed to write the value. + /// + /// The value to be written to PostgreSQL + /// + /// The instance where this value resides. Can be used to access additional + /// information relevant to the write process (e.g. ). + /// + /// The number of bytes required to write the value. + public abstract int ValidateAndGetLength(TPsv value, NpgsqlParameter? parameter); + + /// + /// Writes a value to the provided buffer, with the assumption that there is enough space in the buffer + /// (no I/O will occur). The Npgsql core will have taken care of that. + /// + /// The value to write. + /// The buffer to which to write. + /// + /// The instance where this value resides. Can be used to access additional + /// information relevant to the write process (e.g. ). + /// + public abstract void Write(TPsv value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter); + + #endregion Write + + #region Misc + + public override Type GetProviderSpecificFieldType(FieldDescription? fieldDescription = null) + => typeof(TPsv); + + /// + public override NpgsqlTypeHandler CreateArrayHandler(PostgresArrayType pgArrayType, ArrayNullabilityMode arrayNullabilityMode) + => new ArrayHandlerWithPsv(pgArrayType, this, arrayNullabilityMode); + + #endregion Misc +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandling/NpgsqlTypeHandler.cs b/LibExternal/Npgsql/Internal/TypeHandling/NpgsqlTypeHandler.cs new file mode 100644 index 0000000..f799a47 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandling/NpgsqlTypeHandler.cs @@ -0,0 +1,289 @@ +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandlers; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandling; + +/// +/// Base class for all type handlers, which read and write CLR types into their PostgreSQL +/// binary representation. +/// Type handler writers shouldn't inherit from this class, inherit +/// or instead. +/// +public abstract class NpgsqlTypeHandler +{ + protected NpgsqlTypeHandler(PostgresType postgresType) + => PostgresType = postgresType; + + /// + /// The PostgreSQL type handled by this type handler. + /// + public PostgresType PostgresType { get; } + + #region Read + + /// + /// Reads a value of type with the given length from the provided buffer, + /// using either sync or async I/O. + /// + /// The buffer from which to read. + /// The byte length of the value. The buffer might not contain the full length, requiring I/O to be performed. + /// If I/O is required to read the full length of the value, whether it should be performed synchronously or asynchronously. + /// Additional PostgreSQL information about the type, such as the length in varchar(30). + /// The fully-read value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected internal async ValueTask Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + { + switch (this) + { + case INpgsqlSimpleTypeHandler simpleTypeHandler: + await buf.Ensure(len, async); + return simpleTypeHandler.Read(buf, len, fieldDescription); + case INpgsqlTypeHandler typeHandler: + return await typeHandler.Read(buf, len, async, fieldDescription); + default: + return await ReadCustom(buf, len, async, fieldDescription); + } + } + + /// + /// Version of that's called when we know the entire value + /// is already buffered in memory (i.e. in non-sequential mode). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TAny Read(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + { + Debug.Assert(buf.ReadBytesLeft >= len); + + return this switch + { + INpgsqlSimpleTypeHandler simpleTypeHandler => simpleTypeHandler.Read(buf, len, fieldDescription), + INpgsqlTypeHandler typeHandler => typeHandler.Read(buf, len, async: false, fieldDescription).Result, + _ => ReadCustom(buf, len, async: false, fieldDescription).Result + }; + } + + protected internal virtual ValueTask ReadCustom(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription) + => throw new InvalidCastException(fieldDescription == null + ? $"Can't cast database type to {typeof(TAny).Name}" + : $"Can't cast database type {fieldDescription.Handler.PgDisplayName} to {typeof(TAny).Name}"); + + /// + /// Reads a column as the type handler's default read type. If it is not already entirely in + /// memory, sync or async I/O will be performed as specified by . + /// + public abstract ValueTask ReadAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null); + + /// + /// Version of that's called when we know the entire value + /// is already buffered in memory (i.e. in non-sequential mode). + /// + internal object ReadAsObject(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + { + Debug.Assert(buf.ReadBytesLeft >= len); + + return ReadAsObject(buf, len, async: false, fieldDescription).Result; + } + + /// + /// Reads a column as the type handler's provider-specific type. If it is not already entirely in + /// memory, sync or async I/O will be performed as specified by . + /// + internal virtual ValueTask ReadPsvAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + => ReadAsObject(buf, len, async, fieldDescription); + + /// + /// Version of that's called when we know the entire value + /// is already buffered in memory (i.e. in non-sequential mode). + /// + internal virtual object ReadPsvAsObject(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null) + { + Debug.Assert(buf.ReadBytesLeft >= len); + + return ReadPsvAsObject(buf, len, async: false, fieldDescription).Result; + } + + /// + /// Reads a value from the buffer, assuming our read position is at the value's preceding length. + /// If the length is -1 (null), this method will return the default value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal async ValueTask ReadWithLength(NpgsqlReadBuffer buf, bool async, FieldDescription? fieldDescription = null) + { + await buf.Ensure(4, async); + var len = buf.ReadInt32(); + return len == -1 + ? default! + : NullableHandler.Exists + ? await NullableHandler.ReadAsync(this, buf, len, async, fieldDescription) + : await Read(buf, len, async, fieldDescription); + } + + #endregion + + #region Write + + /// + /// Called to validate and get the length of a value of a generic . + /// and must be handled before calling into this. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected internal int ValidateAndGetLength( + [DisallowNull] TAny value, [NotNullIfNotNull("lengthCache")] ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + { + Debug.Assert(value is not DBNull); + + return this switch + { + INpgsqlSimpleTypeHandler simpleTypeHandler => simpleTypeHandler.ValidateAndGetLength(value, parameter), + INpgsqlTypeHandler typeHandler => typeHandler.ValidateAndGetLength(value, ref lengthCache, parameter), + _ => ValidateAndGetLengthCustom(value, ref lengthCache, parameter) + }; + } + + protected internal virtual int ValidateAndGetLengthCustom( + [DisallowNull] TAny value, [NotNullIfNotNull("lengthCache")] ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + { + var parameterName = parameter is null + ? null + : parameter.TrimmedName == string.Empty + ? parameter.Collection is { } paramCollection + ? $"${paramCollection.IndexOf(parameter) + 1}" + : null // in case of COPY operations parameter isn't bound to a collection + : parameter.TrimmedName; + + throw new InvalidCastException(parameterName is null + ? $"Cannot write a value of CLR type '{typeof(TAny)}' as database type '{PgDisplayName}'." + : $"Cannot write a value of CLR type '{typeof(TAny)}' as database type '{PgDisplayName}' for parameter '{parameterName}'."); + } + + /// + /// Called to write the value of a generic . + /// + /// + /// In the vast majority of cases writing a parameter to the buffer won't need to perform I/O. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public async Task WriteWithLength(TAny? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + { + // TODO: Possibly do a sync path when we don't do I/O (e.g. simple type handler, no flush) + if (buf.WriteSpaceLeft < 4) + await buf.Flush(async, cancellationToken); + + if (value is null or DBNull) + { + buf.WriteInt32(-1); + return; + } + + switch (this) + { + case INpgsqlSimpleTypeHandler simpleTypeHandler: + var len = simpleTypeHandler.ValidateAndGetLength(value, parameter); + buf.WriteInt32(len); + if (buf.WriteSpaceLeft < len) + await buf.Flush(async, cancellationToken); + simpleTypeHandler.Write(value, buf, parameter); + return; + case INpgsqlTypeHandler typeHandler: + buf.WriteInt32(typeHandler.ValidateAndGetLength(value, ref lengthCache, parameter)); + await typeHandler.Write(value, buf, lengthCache, parameter, async, cancellationToken); + return; + default: + await WriteWithLengthCustom(value, buf, lengthCache, parameter, async, cancellationToken); + return; + } + } + + /// + /// Typically does not need to be overridden by type handlers, but may be needed in some + /// cases (e.g. . + /// Note that this method assumes it can write 4 bytes of length (already verified by + /// ). + /// + protected virtual Task WriteWithLengthCustom( + [DisallowNull] TAny value, + NpgsqlWriteBuffer buf, + NpgsqlLengthCache? lengthCache, + NpgsqlParameter? parameter, + bool async, + CancellationToken cancellationToken) + => throw new InvalidCastException($"Can't write '{typeof(TAny).Name}' with type handler '{GetType().Name}'"); + + /// + /// Responsible for validating that a value represents a value of the correct and which can be + /// written for PostgreSQL - if the value cannot be written for any reason, an exception shold be thrown. + /// Also returns the byte length needed to write the value. + /// + /// The value to be written to PostgreSQL + /// + /// If the byte length calculation is costly (e.g. for UTF-8 strings), its result can be stored in the + /// length cache to be reused in the writing process, preventing recalculation. + /// + /// + /// The instance where this value resides. Can be used to access additional + /// information relevant to the write process (e.g. ). + /// + /// The number of bytes required to write the value. + // Source-generated + public abstract int ValidateObjectAndGetLength(object value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter); + + /// + /// Writes a value to the provided buffer, using either sync or async I/O. + /// + /// The value to write. + /// The buffer to which to write. + /// + /// + /// The instance where this value resides. Can be used to access additional + /// information relevant to the write process (e.g. ). + /// + /// If I/O is required to read the full length of the value, whether it should be performed synchronously or asynchronously. + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + // Source-generated + public abstract Task WriteObjectWithLength(object? value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default); + + #endregion Write + + #region Misc + + public abstract Type GetFieldType(FieldDescription? fieldDescription = null); + public abstract Type GetProviderSpecificFieldType(FieldDescription? fieldDescription = null); + + internal virtual bool PreferTextWrite => false; + + /// + /// Creates a type handler for arrays of this handler's type. + /// + public abstract NpgsqlTypeHandler CreateArrayHandler(PostgresArrayType pgArrayType, ArrayNullabilityMode arrayNullabilityMode); + + /// + /// Creates a type handler for ranges of this handler's type. + /// + public abstract NpgsqlTypeHandler CreateRangeHandler(PostgresType pgRangeType); + + /// + /// Creates a type handler for multiranges of this handler's type. + /// + public abstract NpgsqlTypeHandler CreateMultirangeHandler(PostgresMultirangeType pgMultirangeType); + + /// + /// Used to create an exception when the provided type can be converted and written, but an + /// instance of is required for caching of the converted value + /// (in . + /// + protected Exception CreateConversionButNoParamException(Type clrType) + => new InvalidCastException($"Can't convert .NET type '{clrType}' to PostgreSQL '{PgDisplayName}' within an array"); + + internal string PgDisplayName => PostgresType.DisplayName; + + #endregion Misc +} diff --git a/LibExternal/Npgsql/Internal/TypeHandling/NpgsqlTypeHandler`.cs b/LibExternal/Npgsql/Internal/TypeHandling/NpgsqlTypeHandler`.cs new file mode 100644 index 0000000..490ba5f --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandling/NpgsqlTypeHandler`.cs @@ -0,0 +1,74 @@ +using System.Data.Common; +using Npgsql.BackendMessages; +using Npgsql.Internal.TypeHandlers; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeHandling; + +/// +/// Base class for all type handlers, which read and write CLR types into their PostgreSQL +/// binary representation. Unless your type is arbitrary-length, consider inheriting from +/// instead. +/// +/// +/// The default CLR type that this handler will read and write. For example, calling +/// on a column with this handler will return a value with type . +/// Type handlers can support additional types by implementing . +/// +public abstract class NpgsqlTypeHandler : NpgsqlTypeHandler, INpgsqlTypeHandler +{ + protected NpgsqlTypeHandler(PostgresType postgresType) : base(postgresType) { } + + #region Read + + /// + /// Reads a value of type with the given length from the provided buffer, + /// using either sync or async I/O. + /// + /// The buffer from which to read. + /// The byte length of the value. The buffer might not contain the full length, requiring I/O to be performed. + /// If I/O is required to read the full length of the value, whether it should be performed synchronously or asynchronously. + /// Additional PostgreSQL information about the type, such as the length in varchar(30). + /// The fully-read value. + public abstract ValueTask Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null); + + // Since TAny isn't constrained to class? or struct (C# doesn't have a non-nullable constraint that doesn't limit us to either struct or class), + // we must use the bang operator here to tell the compiler that a null value will never returned. + public override async ValueTask ReadAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null) + => (await Read(buf, len, async, fieldDescription))!; + + #endregion Read + + #region Write + + /// + /// Called to validate and get the length of a value of a generic . + /// + public abstract int ValidateAndGetLength(TDefault value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter); + + /// + /// Called to write the value of a generic . + /// + public abstract Task Write(TDefault value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default); + + #endregion Write + + #region Misc + + public override Type GetFieldType(FieldDescription? fieldDescription = null) => typeof(TDefault); + public override Type GetProviderSpecificFieldType(FieldDescription? fieldDescription = null) => typeof(TDefault); + + /// + public override NpgsqlTypeHandler CreateArrayHandler(PostgresArrayType pgArrayType, ArrayNullabilityMode arrayNullabilityMode) + => new ArrayHandler(pgArrayType, this, arrayNullabilityMode); + + /// + public override NpgsqlTypeHandler CreateRangeHandler(PostgresType pgRangeType) + => new RangeHandler(pgRangeType, this); + + /// + public override NpgsqlTypeHandler CreateMultirangeHandler(PostgresMultirangeType pgMultirangeType) + => new MultirangeHandler(pgMultirangeType, (RangeHandler)CreateRangeHandler(pgMultirangeType.Subrange)); + + #endregion Misc +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandling/NullableHandler.cs b/LibExternal/Npgsql/Internal/TypeHandling/NullableHandler.cs new file mode 100644 index 0000000..e3bb061 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandling/NullableHandler.cs @@ -0,0 +1,69 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; + +// ReSharper disable StaticMemberInGenericType +namespace Npgsql.Internal.TypeHandling; + +delegate T ReadDelegate(NpgsqlTypeHandler handler, NpgsqlReadBuffer buffer, int columnLength, FieldDescription? fieldDescription = null); +delegate ValueTask ReadAsyncDelegate(NpgsqlTypeHandler handler, NpgsqlReadBuffer buffer, int columnLen, bool async, FieldDescription? fieldDescription = null); + +delegate int ValidateAndGetLengthDelegate(NpgsqlTypeHandler handler, T value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter); +delegate Task WriteAsyncDelegate(NpgsqlTypeHandler handler, T value, NpgsqlWriteBuffer buffer, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default); + +static class NullableHandler +{ + public static readonly Type? UnderlyingType; + public static readonly ReadDelegate Read = null!; + public static readonly ReadAsyncDelegate ReadAsync = null!; + public static readonly ValidateAndGetLengthDelegate ValidateAndGetLength = null!; + public static readonly WriteAsyncDelegate WriteAsync = null!; + + public static bool Exists => UnderlyingType != null; + + static NullableHandler() + { + UnderlyingType = Nullable.GetUnderlyingType(typeof(T)); + + if (UnderlyingType == null) + return; + + Read = NullableHandler.CreateDelegate>(UnderlyingType, NullableHandler.ReadMethod); + ReadAsync = NullableHandler.CreateDelegate>(UnderlyingType, NullableHandler.ReadAsyncMethod); + ValidateAndGetLength = NullableHandler.CreateDelegate>(UnderlyingType, NullableHandler.ValidateMethod); + WriteAsync = NullableHandler.CreateDelegate>(UnderlyingType, NullableHandler.WriteAsyncMethod); + } +} + +static class NullableHandler +{ + internal static readonly MethodInfo ReadMethod = new ReadDelegate(Read).Method.GetGenericMethodDefinition(); + internal static readonly MethodInfo ReadAsyncMethod = new ReadAsyncDelegate(ReadAsync).Method.GetGenericMethodDefinition(); + internal static readonly MethodInfo ValidateMethod = new ValidateAndGetLengthDelegate(ValidateAndGetLength).Method.GetGenericMethodDefinition(); + internal static readonly MethodInfo WriteAsyncMethod = new WriteAsyncDelegate(WriteAsync).Method.GetGenericMethodDefinition(); + + static T? Read(NpgsqlTypeHandler handler, NpgsqlReadBuffer buffer, int columnLength, FieldDescription? fieldDescription) + where T : struct + => handler.Read(buffer, columnLength, fieldDescription); + + static async ValueTask ReadAsync(NpgsqlTypeHandler handler, NpgsqlReadBuffer buffer, int columnLength, bool async, FieldDescription? fieldDescription) + where T : struct + => await handler.Read(buffer, columnLength, async, fieldDescription); + + static int ValidateAndGetLength(NpgsqlTypeHandler handler, T? value, ref NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter) + where T : struct + => value.HasValue ? handler.ValidateAndGetLength(value.Value, ref lengthCache, parameter) : 0; + + static Task WriteAsync(NpgsqlTypeHandler handler, T? value, NpgsqlWriteBuffer buffer, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default) + where T : struct + => value.HasValue + ? handler.WriteWithLength(value.Value, buffer, lengthCache, parameter, async, cancellationToken) + : handler.WriteWithLength(DBNull.Value, buffer, lengthCache, parameter, async, cancellationToken); + + internal static TDelegate CreateDelegate(Type underlyingType, MethodInfo method) + where TDelegate : Delegate + => (TDelegate)method.MakeGenericMethod(underlyingType).CreateDelegate(typeof(TDelegate)); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandling/TypeHandlerResolver.cs b/LibExternal/Npgsql/Internal/TypeHandling/TypeHandlerResolver.cs new file mode 100644 index 0000000..4170f75 --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandling/TypeHandlerResolver.cs @@ -0,0 +1,30 @@ +using System; + +namespace Npgsql.Internal.TypeHandling; + +/// +/// An Npgsql resolver for type handlers. Typically used by plugins to alter how Npgsql reads and writes values to PostgreSQL. +/// +public abstract class TypeHandlerResolver +{ + /// + /// Resolves a type handler given a PostgreSQL type name, corresponding to the typname column in the PostgreSQL pg_type catalog table. + /// + /// See . + public abstract NpgsqlTypeHandler? ResolveByDataTypeName(string typeName); + + /// + /// Resolves a type handler given a .NET CLR type. + /// + public abstract NpgsqlTypeHandler? ResolveByClrType(Type type); + + public virtual NpgsqlTypeHandler? ResolveValueDependentValue(object value) => null; + + public virtual NpgsqlTypeHandler? ResolveValueTypeGenerically(T value) => null; + + /// + /// Gets type mapping information for a given PostgreSQL type. + /// Invoked in scenarios when mapping information is required, rather than a type handler for reading or writing. + /// + public abstract TypeMappingInfo? GetMappingByDataTypeName(string dataTypeName); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandling/TypeHandlerResolverFactory.cs b/LibExternal/Npgsql/Internal/TypeHandling/TypeHandlerResolverFactory.cs new file mode 100644 index 0000000..78d3b5b --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandling/TypeHandlerResolverFactory.cs @@ -0,0 +1,21 @@ +using System; + +namespace Npgsql.Internal.TypeHandling; + +public abstract class TypeHandlerResolverFactory +{ + public abstract TypeHandlerResolver Create(NpgsqlConnector connector); + + public abstract string? GetDataTypeNameByClrType(Type clrType); + public virtual string? GetDataTypeNameByValueDependentValue(object value) => null; + public abstract TypeMappingInfo? GetMappingByDataTypeName(string dataTypeName); +} + +static class TypeHandlerResolverFactoryExtensions +{ + internal static TypeMappingInfo? GetMappingByClrType(this TypeHandlerResolverFactory factory, Type clrType) + => factory.GetDataTypeNameByClrType(clrType) is { } dataTypeName ? factory.GetMappingByDataTypeName(dataTypeName) : null; + + internal static TypeMappingInfo? GetMappingByValueDependentValue(this TypeHandlerResolverFactory factory, object value) + => factory.GetDataTypeNameByValueDependentValue(value) is { } dataTypeName ? factory.GetMappingByDataTypeName(dataTypeName) : null; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeHandling/TypeMappingInfo.cs b/LibExternal/Npgsql/Internal/TypeHandling/TypeMappingInfo.cs new file mode 100644 index 0000000..8d61d1e --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeHandling/TypeMappingInfo.cs @@ -0,0 +1,22 @@ +using System; +using System.Data; +using Npgsql.TypeMapping; +using NpgsqlTypes; + +namespace Npgsql.Internal.TypeHandling; + +public class TypeMappingInfo +{ + public TypeMappingInfo(NpgsqlDbType? npgsqlDbType, string? dataTypeName, Type clrType) + => (NpgsqlDbType, DataTypeName, ClrTypes) = (npgsqlDbType, dataTypeName, new[] { clrType }); + + public TypeMappingInfo(NpgsqlDbType? npgsqlDbType, string? dataTypeName, params Type[] clrTypes) + => (NpgsqlDbType, DataTypeName, ClrTypes) = (npgsqlDbType, dataTypeName, clrTypes); + + public NpgsqlDbType? NpgsqlDbType { get; } + // Note that we can't cache the result due to nullable's assignment not being thread safe + public DbType DbType + => NpgsqlDbType is null ? DbType.Object : GlobalTypeMapper.NpgsqlDbTypeToDbType(NpgsqlDbType.Value); + public string? DataTypeName { get; } + public Type[] ClrTypes { get; } +} diff --git a/LibExternal/Npgsql/Internal/TypeMapping/IUserTypeMapping.cs b/LibExternal/Npgsql/Internal/TypeMapping/IUserTypeMapping.cs new file mode 100644 index 0000000..aedc14e --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeMapping/IUserTypeMapping.cs @@ -0,0 +1,13 @@ +using System; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeMapping; + +public interface IUserTypeMapping +{ + public string PgTypeName { get; } + public Type ClrType { get; } + + public NpgsqlTypeHandler CreateHandler(PostgresType pgType, NpgsqlConnector connector); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeMapping/UserCompositeTypeMappings.cs b/LibExternal/Npgsql/Internal/TypeMapping/UserCompositeTypeMappings.cs new file mode 100644 index 0000000..97bf9ec --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeMapping/UserCompositeTypeMappings.cs @@ -0,0 +1,24 @@ +using System; +using Npgsql.Internal.TypeHandlers.CompositeHandlers; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; + +namespace Npgsql.Internal.TypeMapping; + +public interface IUserCompositeTypeMapping : IUserTypeMapping +{ + INpgsqlNameTranslator NameTranslator { get; } +} + +class UserCompositeTypeMapping : IUserCompositeTypeMapping +{ + public string PgTypeName { get; } + public Type ClrType => typeof(T); + public INpgsqlNameTranslator NameTranslator { get; } + + public UserCompositeTypeMapping(string pgTypeName, INpgsqlNameTranslator nameTranslator) + => (PgTypeName, NameTranslator) = (pgTypeName, nameTranslator); + + public NpgsqlTypeHandler CreateHandler(PostgresType pgType, NpgsqlConnector connector) + => new CompositeHandler((PostgresCompositeType)pgType, connector.TypeMapper, NameTranslator); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Internal/TypeMapping/UserEnumTypeMappings.cs b/LibExternal/Npgsql/Internal/TypeMapping/UserEnumTypeMappings.cs new file mode 100644 index 0000000..b28420d --- /dev/null +++ b/LibExternal/Npgsql/Internal/TypeMapping/UserEnumTypeMappings.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Npgsql.Internal.TypeHandlers; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using NpgsqlTypes; + +namespace Npgsql.Internal.TypeMapping; + +public interface IUserEnumTypeMapping : IUserTypeMapping +{ + INpgsqlNameTranslator NameTranslator { get; } +} + +class UserEnumTypeMapping : IUserEnumTypeMapping + where TEnum : struct, Enum +{ + public string PgTypeName { get; } + public Type ClrType => typeof(TEnum); + public INpgsqlNameTranslator NameTranslator { get; } + + readonly Dictionary _enumToLabel = new(); + readonly Dictionary _labelToEnum = new(); + + public UserEnumTypeMapping(string pgTypeName, INpgsqlNameTranslator nameTranslator) + { + (PgTypeName, NameTranslator) = (pgTypeName, nameTranslator); + + foreach (var field in typeof(TEnum).GetFields(BindingFlags.Static | BindingFlags.Public)) + { + var attribute = (PgNameAttribute?)field.GetCustomAttributes(typeof(PgNameAttribute), false).FirstOrDefault(); + var enumName = attribute is null + ? nameTranslator.TranslateMemberName(field.Name) + : attribute.PgName; + var enumValue = (TEnum)field.GetValue(null)!; + + _enumToLabel[enumValue] = enumName; + _labelToEnum[enumName] = enumValue; + } + } + + public NpgsqlTypeHandler CreateHandler(PostgresType postgresType, NpgsqlConnector connector) + => new EnumHandler((PostgresEnumType)postgresType, _enumToLabel, _labelToEnum); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/KerberosUsernameProvider.cs b/LibExternal/Npgsql/KerberosUsernameProvider.cs new file mode 100644 index 0000000..49344f7 --- /dev/null +++ b/LibExternal/Npgsql/KerberosUsernameProvider.cs @@ -0,0 +1,92 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using Npgsql.Logging; + +namespace Npgsql; + +/// +/// Launches MIT Kerberos klist and parses out the default principal from it. +/// Caches the result. +/// +class KerberosUsernameProvider +{ + static bool _performedDetection; + static string? _principalWithRealm; + static string? _principalWithoutRealm; + + static readonly NpgsqlLogger Log = NpgsqlLogManager.CreateLogger(nameof(KerberosUsernameProvider)); + + internal static string? GetUsername(bool includeRealm) + { + if (!_performedDetection) + { + DetectUsername(); + _performedDetection = true; + } + return includeRealm ? _principalWithRealm : _principalWithoutRealm; + } + + static void DetectUsername() + { + var klistPath = FindInPath("klist"); + if (klistPath == null) + { + Log.Debug("klist not found in PATH, skipping Kerberos username detection"); + return; + } + + var processStartInfo = new ProcessStartInfo + { + FileName = klistPath, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false + }; + var process = Process.Start(processStartInfo); + if (process is null) + { + Log.Debug($"klist process could not be started"); + return; + } + + process.WaitForExit(); + if (process.ExitCode != 0) + { + Log.Debug($"klist exited with code {process.ExitCode}: {process.StandardError.ReadToEnd()}"); + return; + } + + var line = default(string); + for (var i = 0; i < 2; i++) + if ((line = process.StandardOutput.ReadLine()) == null) + { + Log.Debug("Unexpected output from klist, aborting Kerberos username detection"); + return; + } + + var components = line!.Split(':'); + if (components.Length != 2) + { + Log.Debug("Unexpected output from klist, aborting Kerberos username detection"); + return; + } + + var principalWithRealm = components[1].Trim(); + components = principalWithRealm.Split('@'); + if (components.Length != 2) + { + Log.Debug($"Badly-formed default principal {principalWithRealm} from klist, aborting Kerberos username detection"); + return; + } + + _principalWithRealm = principalWithRealm; + _principalWithoutRealm = components[0]; + } + + static string? FindInPath(string name) => Environment.GetEnvironmentVariable("PATH") + ?.Split(Path.PathSeparator) + .Select(p => Path.Combine(p, name)) + .FirstOrDefault(File.Exists); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Logging/INpgsqlLoggingProvider.cs b/LibExternal/Npgsql/Logging/INpgsqlLoggingProvider.cs new file mode 100644 index 0000000..da28a25 --- /dev/null +++ b/LibExternal/Npgsql/Logging/INpgsqlLoggingProvider.cs @@ -0,0 +1,10 @@ +namespace Npgsql.Logging; + +/// Used to create logger instances of the given name. +public interface INpgsqlLoggingProvider +{ + /// + /// Creates a new INpgsqlLogger instance of the given name. + /// + NpgsqlLogger CreateLogger(string name); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Logging/NoOpLoggingProvider.cs b/LibExternal/Npgsql/Logging/NoOpLoggingProvider.cs new file mode 100644 index 0000000..1f30568 --- /dev/null +++ b/LibExternal/Npgsql/Logging/NoOpLoggingProvider.cs @@ -0,0 +1,19 @@ +using System; + +namespace Npgsql.Logging; + +class NoOpLoggingProvider : INpgsqlLoggingProvider +{ + public NpgsqlLogger CreateLogger(string name) => NoOpLogger.Instance; +} + +class NoOpLogger : NpgsqlLogger +{ + internal static NoOpLogger Instance = new(); + + NoOpLogger() {} + public override bool IsEnabled(NpgsqlLogLevel level) => false; + public override void Log(NpgsqlLogLevel level, int connectorId, string msg, Exception? exception = null) + { + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Logging/NpgsqlLogLevel.cs b/LibExternal/Npgsql/Logging/NpgsqlLogLevel.cs new file mode 100644 index 0000000..239eb5a --- /dev/null +++ b/LibExternal/Npgsql/Logging/NpgsqlLogLevel.cs @@ -0,0 +1,13 @@ +#pragma warning disable 1591 + +namespace Npgsql.Logging; + +public enum NpgsqlLogLevel +{ + Trace = 1, + Debug = 2, + Info = 3, + Warn = 4, + Error = 5, + Fatal = 6, +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Logging/NpgsqlLogManager.cs b/LibExternal/Npgsql/Logging/NpgsqlLogManager.cs new file mode 100644 index 0000000..c21c4ff --- /dev/null +++ b/LibExternal/Npgsql/Logging/NpgsqlLogManager.cs @@ -0,0 +1,39 @@ +using System; + +namespace Npgsql.Logging; + +/// +/// Manages logging for Npgsql, used to set the logging provider. +/// +public static class NpgsqlLogManager +{ + /// + /// The logging provider used for logging in Npgsql. + /// + public static INpgsqlLoggingProvider Provider + { + get + { + _providerRetrieved = true; + return _provider; + } + set + { + if (_providerRetrieved) + throw new InvalidOperationException("The logging provider must be set before any Npgsql action is taken"); + + _provider = value ?? throw new ArgumentNullException(nameof(value)); + } + } + + /// + /// Determines whether parameter contents will be logged alongside SQL statements - this may reveal sensitive information. + /// Defaults to false. + /// + public static bool IsParameterLoggingEnabled { get; set; } + + static INpgsqlLoggingProvider _provider = new NoOpLoggingProvider(); + static bool _providerRetrieved; + + internal static NpgsqlLogger CreateLogger(string name) => Provider.CreateLogger("Npgsql." + name); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Logging/NpgsqlLogger.cs b/LibExternal/Npgsql/Logging/NpgsqlLogger.cs new file mode 100644 index 0000000..f603688 --- /dev/null +++ b/LibExternal/Npgsql/Logging/NpgsqlLogger.cs @@ -0,0 +1,28 @@ +using System; + +#pragma warning disable 1591 + +namespace Npgsql.Logging; + +/// +/// A generic interface for logging. +/// +public abstract class NpgsqlLogger +{ + public abstract bool IsEnabled(NpgsqlLogLevel level); + public abstract void Log(NpgsqlLogLevel level, int connectorId, string msg, Exception? exception = null); + + internal void Trace(string msg, int connectionId = 0) => Log(NpgsqlLogLevel.Trace, connectionId, msg); + internal void Debug(string msg, int connectionId = 0) => Log(NpgsqlLogLevel.Debug, connectionId, msg); + internal void Info(string msg, int connectionId = 0) => Log(NpgsqlLogLevel.Info, connectionId, msg); + internal void Warn(string msg, int connectionId = 0) => Log(NpgsqlLogLevel.Warn, connectionId, msg); + internal void Error(string msg, int connectionId = 0) => Log(NpgsqlLogLevel.Error, connectionId, msg); + internal void Fatal(string msg, int connectionId = 0) => Log(NpgsqlLogLevel.Fatal, connectionId, msg); + + internal void Trace(string msg, Exception ex, int connectionId = 0) => Log(NpgsqlLogLevel.Trace, connectionId, msg, ex); + internal void Debug(string msg, Exception ex, int connectionId = 0) => Log(NpgsqlLogLevel.Debug, connectionId, msg, ex); + internal void Info(string msg, Exception ex, int connectionId = 0) => Log(NpgsqlLogLevel.Info, connectionId, msg, ex); + internal void Warn(string msg, Exception ex, int connectionId = 0) => Log(NpgsqlLogLevel.Warn, connectionId, msg, ex); + internal void Error(string msg, Exception ex, int connectionId = 0) => Log(NpgsqlLogLevel.Error, connectionId, msg, ex); + internal void Fatal(string msg, Exception ex, int connectionId = 0) => Log(NpgsqlLogLevel.Fatal, connectionId, msg, ex); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/MultiHostConnectorPool.cs b/LibExternal/Npgsql/MultiHostConnectorPool.cs new file mode 100644 index 0000000..b3df2e0 --- /dev/null +++ b/LibExternal/Npgsql/MultiHostConnectorPool.cs @@ -0,0 +1,337 @@ +using Npgsql.Internal; +using Npgsql.Util; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Transactions; + +namespace Npgsql; + +sealed class MultiHostConnectorPool : ConnectorSource +{ + internal override bool OwnsConnectors => false; + + readonly ConnectorSource[] _pools; + + volatile int _roundRobinIndex = -1; + + public MultiHostConnectorPool(NpgsqlConnectionStringBuilder settings, string connString) : base(settings, connString) + { + var hosts = settings.Host!.Split(','); + _pools = new ConnectorSource[hosts.Length]; + for (var i = 0; i < hosts.Length; i++) + { + var poolSettings = settings.Clone(); + Debug.Assert(!poolSettings.Multiplexing); + var host = hosts[i].AsSpan().Trim(); + if (NpgsqlConnectionStringBuilder.TrySplitHostPort(host, out var newHost, out var newPort)) + { + poolSettings.Host = newHost; + poolSettings.Port = newPort; + } + else + poolSettings.Host = host.ToString(); + + _pools[i] = settings.Pooling + ? new ConnectorPool(poolSettings, poolSettings.ConnectionString, this) + : new UnpooledConnectorSource(poolSettings, poolSettings.ConnectionString); + } + } + + static bool IsPreferred(ClusterState state, TargetSessionAttributes preferredType) + => state switch + { + ClusterState.Offline => false, + ClusterState.Unknown => true, // We will check compatibility again after refreshing the cluster state + ClusterState.PrimaryReadWrite when preferredType == TargetSessionAttributes.Primary || preferredType == TargetSessionAttributes.PreferPrimary + || preferredType == TargetSessionAttributes.ReadWrite => true, + ClusterState.PrimaryReadOnly when preferredType == TargetSessionAttributes.Primary || preferredType == TargetSessionAttributes.PreferPrimary + || preferredType == TargetSessionAttributes.ReadOnly => true, + ClusterState.Standby when preferredType == TargetSessionAttributes.Standby || preferredType == TargetSessionAttributes.PreferStandby + || preferredType == TargetSessionAttributes.ReadOnly => true, + _ => preferredType == TargetSessionAttributes.Any + }; + + static bool IsOnline(ClusterState state, TargetSessionAttributes preferredType) + { + Debug.Assert(preferredType is TargetSessionAttributes.PreferPrimary or TargetSessionAttributes.PreferStandby); + return state != ClusterState.Offline; + } + + static ClusterState GetClusterState(ConnectorSource pool, bool ignoreExpiration = false) + => GetClusterState(pool.Settings.Host!, pool.Settings.Port, ignoreExpiration); + + static ClusterState GetClusterState(string host, int port, bool ignoreExpiration) + => ClusterStateCache.GetClusterState(host, port, ignoreExpiration); + + async ValueTask TryGetIdleOrNew(NpgsqlConnection conn, TimeSpan timeoutPerHost, bool async, + TargetSessionAttributes preferredType, Func clusterValidator, int poolIndex, + IList exceptions, CancellationToken cancellationToken) + { + var pools = _pools; + for (var i = 0; i < pools.Length; i++) + { + var pool = pools[poolIndex]; + poolIndex++; + if (poolIndex == pools.Length) + poolIndex = 0; + + var clusterState = GetClusterState(pool); + if (!clusterValidator(clusterState, preferredType)) + continue; + + NpgsqlConnector? connector = null; + + try + { + if (pool.TryGetIdleConnector(out connector)) + { + if (clusterState == ClusterState.Unknown) + { + clusterState = await connector.QueryClusterState(new NpgsqlTimeout(timeoutPerHost), async, cancellationToken); + Debug.Assert(clusterState != ClusterState.Unknown); + if (!clusterValidator(clusterState, preferredType)) + { + pool.Return(connector); + continue; + } + } + + return connector; + } + else + { + connector = await pool.OpenNewConnector(conn, new NpgsqlTimeout(timeoutPerHost), async, cancellationToken); + if (connector is not null) + { + if (clusterState == ClusterState.Unknown) + { + // While opening a new connector we might have refreshed the cluster state, check again + clusterState = GetClusterState(pool); + if (clusterState == ClusterState.Unknown) + clusterState = await connector.QueryClusterState(new NpgsqlTimeout(timeoutPerHost), async, cancellationToken); + Debug.Assert(clusterState != ClusterState.Unknown); + if (!clusterValidator(clusterState, preferredType)) + { + pool.Return(connector); + continue; + } + } + + return connector; + } + } + } + catch (Exception ex) + { + exceptions.Add(ex); + if (connector is not null) + pool.Return(connector); + } + } + + return null; + } + + async ValueTask TryGet(NpgsqlConnection conn, TimeSpan timeoutPerHost, bool async, TargetSessionAttributes preferredType, + Func clusterValidator, int poolIndex, + IList exceptions, CancellationToken cancellationToken) + { + var pools = _pools; + for (var i = 0; i < pools.Length; i++) + { + var pool = pools[poolIndex]; + poolIndex++; + if (poolIndex == pools.Length) + poolIndex = 0; + + var clusterState = GetClusterState(pool); + if (!clusterValidator(clusterState, preferredType)) + continue; + + NpgsqlConnector? connector = null; + + try + { + connector = await pool.Get(conn, new NpgsqlTimeout(timeoutPerHost), async, cancellationToken); + if (clusterState == ClusterState.Unknown) + { + // Get might have opened a new physical connection and refreshed the cluster state, check again + clusterState = GetClusterState(pool); + if (clusterState == ClusterState.Unknown) + clusterState = await connector.QueryClusterState(new NpgsqlTimeout(timeoutPerHost), async, cancellationToken); + + Debug.Assert(clusterState != ClusterState.Unknown); + if (!clusterValidator(clusterState, preferredType)) + { + pool.Return(connector); + continue; + } + } + + return connector; + } + catch (Exception ex) + { + exceptions.Add(ex); + if (connector is not null) + pool.Return(connector); + } + } + + return null; + } + + internal override async ValueTask Get(NpgsqlConnection conn, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken) + { + var exceptions = new List(); + + var poolIndex = conn.Settings.LoadBalanceHosts ? GetRoundRobinIndex() : 0; + + var timeoutPerHost = timeout.IsSet ? timeout.CheckAndGetTimeLeft() : TimeSpan.Zero; + var preferredType = GetTargetSessionAttributes(conn); + var checkUnpreferred = + preferredType == TargetSessionAttributes.PreferPrimary || + preferredType == TargetSessionAttributes.PreferStandby; + + var connector = await TryGetIdleOrNew(conn, timeoutPerHost, async, preferredType, IsPreferred, poolIndex, exceptions, cancellationToken) ?? + (checkUnpreferred ? + await TryGetIdleOrNew(conn, timeoutPerHost, async, preferredType, IsOnline, poolIndex, exceptions, cancellationToken) + : null) ?? + await TryGet(conn, timeoutPerHost, async, preferredType, IsPreferred, poolIndex, exceptions, cancellationToken) ?? + (checkUnpreferred ? + await TryGet(conn, timeoutPerHost, async, preferredType, IsOnline, poolIndex, exceptions, cancellationToken) + : null); + + if (connector is not null) + return connector; + + throw NoSuitableHostsException(exceptions); + } + + static NpgsqlException NoSuitableHostsException(IList exceptions) + => exceptions.Count == 0 + ? new NpgsqlException("No suitable host was found.") + : exceptions[0] is PostgresException firstException && + exceptions.All(x => x is PostgresException ex && ex.SqlState == firstException.SqlState) + ? firstException + : new NpgsqlException("Unable to connect to a suitable host. Check inner exception for more details.", + new AggregateException(exceptions)); + + int GetRoundRobinIndex() + { + while (true) + { + var index = Interlocked.Increment(ref _roundRobinIndex); + if (index >= 0) + return index % _pools.Length; + + // Worst case scenario - we've wrapped around integer counter + if (index == int.MinValue) + { + // This is the thread which wrapped around the counter - reset it to 0 + _roundRobinIndex = 0; + return 0; + } + + // This is not the thread which wrapped around the counter - just wait until it's 0 or more + var sw = new SpinWait(); + while (_roundRobinIndex < 0) + sw.SpinOnce(); + } + } + + internal override void Return(NpgsqlConnector connector) + => throw new NpgsqlException("Npgsql bug: a connector was returned to " + nameof(MultiHostConnectorPool)); + + internal override bool TryGetIdleConnector([NotNullWhen(true)] out NpgsqlConnector? connector) + => throw new NpgsqlException("Npgsql bug: trying to get an idle connector from " + nameof(MultiHostConnectorPool)); + + internal override ValueTask OpenNewConnector(NpgsqlConnection conn, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken) + => throw new NpgsqlException("Npgsql bug: trying to open a new connector from " + nameof(MultiHostConnectorPool)); + + internal override void Clear() + { + foreach (var pool in _pools) + pool.Clear(); + } + + internal override (int Total, int Idle, int Busy) Statistics + { + get + { + var numConnectors = 0; + var idleCount = 0; + + foreach (var pool in _pools) + { + var stat = pool.Statistics; + numConnectors += stat.Total; + idleCount += stat.Idle; + } + + return (numConnectors, idleCount, numConnectors - idleCount); + } + } + + internal override bool TryRentEnlistedPending(Transaction transaction, NpgsqlConnection connection, + [NotNullWhen(true)] out NpgsqlConnector? connector) + { + lock (_pendingEnlistedConnectors) + { + if (!_pendingEnlistedConnectors.TryGetValue(transaction, out var list)) + { + connector = null; + return false; + } + + var preferredType = GetTargetSessionAttributes(connection); + // First try to get a valid preferred connector. + if (TryGetValidConnector(list, preferredType, IsPreferred, out connector)) + { + return true; + } + + // Can't get valid preferred connector. Try to get an unpreferred connector, if supported. + if ((preferredType == TargetSessionAttributes.PreferPrimary || preferredType == TargetSessionAttributes.PreferStandby) + && TryGetValidConnector(list, preferredType, IsOnline, out connector)) + { + return true; + } + + connector = null; + return false; + } + + bool TryGetValidConnector(List list, TargetSessionAttributes preferredType, + Func validationFunc, [NotNullWhen(true)] out NpgsqlConnector? connector) + { + for (var i = list.Count - 1; i >= 0; i--) + { + connector = list[i]; + var lastKnownState = GetClusterState(connector.Host, connector.Port, ignoreExpiration: true); + Debug.Assert(lastKnownState != ClusterState.Unknown); + if (validationFunc(lastKnownState, preferredType)) + { + list.RemoveAt(i); + if (list.Count == 0) + _pendingEnlistedConnectors.Remove(transaction); + return true; + } + } + + connector = null; + return false; + } + } + + static TargetSessionAttributes GetTargetSessionAttributes(NpgsqlConnection connection) + => connection.Settings.TargetSessionAttributesParsed ?? + (PostgresEnvironment.TargetSessionAttributes is { } s + ? NpgsqlConnectionStringBuilder.ParseTargetSessionAttributes(s) + : default); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/MultiHostConnectorPoolWrapper.cs b/LibExternal/Npgsql/MultiHostConnectorPoolWrapper.cs new file mode 100644 index 0000000..f2b0bf4 --- /dev/null +++ b/LibExternal/Npgsql/MultiHostConnectorPoolWrapper.cs @@ -0,0 +1,38 @@ +using Npgsql.Internal; +using Npgsql.Util; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using System.Transactions; + +namespace Npgsql; + +sealed class MultiHostConnectorPoolWrapper : ConnectorSource +{ + internal override bool OwnsConnectors => false; + + readonly MultiHostConnectorPool _wrappedSource; + + public MultiHostConnectorPoolWrapper(NpgsqlConnectionStringBuilder settings, string connString, MultiHostConnectorPool source) : base(settings, connString) + => _wrappedSource = source; + + internal override (int Total, int Idle, int Busy) Statistics => _wrappedSource.Statistics; + + internal override void Clear() => _wrappedSource.Clear(); + internal override ValueTask Get(NpgsqlConnection conn, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken) + => _wrappedSource.Get(conn, timeout, async, cancellationToken); + internal override bool TryGetIdleConnector([NotNullWhen(true)] out NpgsqlConnector? connector) + => throw new NpgsqlException("Npgsql bug: trying to get an idle connector from " + nameof(MultiHostConnectorPoolWrapper)); + internal override ValueTask OpenNewConnector(NpgsqlConnection conn, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken) + => throw new NpgsqlException("Npgsql bug: trying to open a new connector from " + nameof(MultiHostConnectorPoolWrapper)); + internal override void Return(NpgsqlConnector connector) + => _wrappedSource.Return(connector); + + internal override void AddPendingEnlistedConnector(NpgsqlConnector connector, Transaction transaction) + => _wrappedSource.AddPendingEnlistedConnector(connector, transaction); + internal override bool TryRemovePendingEnlistedConnector(NpgsqlConnector connector, Transaction transaction) + => _wrappedSource.TryRemovePendingEnlistedConnector(connector, transaction); + internal override bool TryRentEnlistedPending(Transaction transaction, NpgsqlConnection connection, + [NotNullWhen(true)] out NpgsqlConnector? connector) + => _wrappedSource.TryRentEnlistedPending(transaction, connection, out connector); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/MultiplexingConnectorPool.cs b/LibExternal/Npgsql/MultiplexingConnectorPool.cs new file mode 100644 index 0000000..043d8f6 --- /dev/null +++ b/LibExternal/Npgsql/MultiplexingConnectorPool.cs @@ -0,0 +1,423 @@ +using System.Diagnostics; +using System.Threading.Channels; +using Npgsql.Internal; +using Npgsql.Logging; +using Npgsql.TypeMapping; +using Npgsql.Util; +using static Npgsql.Util.Statics; + +namespace Npgsql; + +sealed class MultiplexingConnectorPool : ConnectorPool +{ + static readonly NpgsqlLogger Log = NpgsqlLogManager.CreateLogger(nameof(MultiplexingConnectorPool)); + + readonly bool _autoPrepare; + + public bool IsBootstrapped + { + get => _isBootstrapped; + set => _isBootstrapped = value; + } + + volatile bool _isBootstrapped; + + readonly ChannelReader _multiplexCommandReader; + internal ChannelWriter MultiplexCommandWriter { get; } + + /// + /// A pool-wide type mapper used when multiplexing. This is necessary because binding parameters + /// to their type handlers happens *before* the command is enqueued for execution, so there's no + /// connector yet at that stage. + /// + internal ConnectorTypeMapper? MultiplexingTypeMapper { get; private set; } + + /// + /// When multiplexing is enabled, determines the maximum number of outgoing bytes to buffer before + /// flushing to the network. + /// + readonly int _writeCoalescingBufferThresholdBytes; + + readonly SemaphoreSlim _bootstrapSemaphore; + + // TODO: Make this configurable + const int MultiplexingCommandChannelBound = 4096; + + internal MultiplexingConnectorPool( + NpgsqlConnectionStringBuilder settings, string connString, MultiHostConnectorPool? parentPool = null) + : base(settings, connString, parentPool) + { + Debug.Assert(Settings.Multiplexing); + + // TODO: Validate multiplexing options are set only when Multiplexing is on + + _autoPrepare = settings.MaxAutoPrepare > 0; + + _bootstrapSemaphore = new SemaphoreSlim(1); + + _writeCoalescingBufferThresholdBytes = Settings.WriteCoalescingBufferThresholdBytes; + + var multiplexCommandChannel = Channel.CreateBounded( + new BoundedChannelOptions(MultiplexingCommandChannelBound) + { + FullMode = BoundedChannelFullMode.Wait, + SingleReader = true + }); + _multiplexCommandReader = multiplexCommandChannel.Reader; + MultiplexCommandWriter = multiplexCommandChannel.Writer; + } + + /// + /// Called exactly once per multiplexing pool, when the first connection is opened, with three goals: + /// 1. Load types and bind the pool-wide type mapper (necessary for binding parameters) + /// 2. Cause any connection exceptions (e.g. bad username) to be thrown from NpgsqlConnection.Open + /// 3. Start the multiplexing write loop after we've made sure that's the exact pool we're going to use + /// + internal async Task BootstrapMultiplexing(NpgsqlConnection conn, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken = default) + { + var hasSemaphore = async + ? await _bootstrapSemaphore!.WaitAsync(timeout.CheckAndGetTimeLeft(), cancellationToken) + : _bootstrapSemaphore!.Wait(timeout.CheckAndGetTimeLeft(), cancellationToken); + + // We've timed out - calling Check, to throw the correct exception + if (!hasSemaphore) + timeout.Check(); + + try + { + if (IsBootstrapped) + return; + + var connector = await conn.StartBindingScope(ConnectorBindingScope.Connection, timeout, async, cancellationToken); + using var _ = Defer(static conn => conn.EndBindingScope(ConnectorBindingScope.Connection), conn); + + MultiplexingTypeMapper = connector.TypeMapper; + + // TODO: Think about cleanup for this, e.g. completing the channel at application shutdown and/or + // pool clearing + var __ = Task.Run(MultiplexingWriteLoop, CancellationToken.None) + .ContinueWith(t => + { + // Note that we *must* observe the exception if the task is faulted. + Log.Error("Exception in multiplexing write loop, this is an Npgsql bug, please file an issue.", + t.Exception!); + }, TaskContinuationOptions.OnlyOnFaulted); + + IsBootstrapped = true; + } + finally + { + _bootstrapSemaphore!.Release(); + } + } + + async Task MultiplexingWriteLoop() + { + // This method is async, but only ever yields when there are no pending commands in the command channel. + // No I/O should ever be performed asynchronously, as that would block further writing for the entire + // application; whenever I/O cannot complete immediately, we chain a callback with ContinueWith and move + // on to the next connector. + Debug.Assert(_multiplexCommandReader != null); + + var stats = new MultiplexingStats { Stopwatch = new Stopwatch() }; + + while (true) + { + NpgsqlConnector? connector; + + // Get a first command out. + if (!_multiplexCommandReader.TryRead(out var command)) + command = await _multiplexCommandReader.ReadAsync(); + + try + { + // First step is to get a connector on which to execute + var spinwait = new SpinWait(); + while (true) + { + if (TryGetIdleConnector(out connector)) + { + // See increment under over-capacity mode below + Interlocked.Increment(ref connector.CommandsInFlightCount); + break; + } + + connector = await OpenNewConnector( + command.Connection!, + new NpgsqlTimeout(TimeSpan.FromSeconds(Settings.Timeout)), + async: true, + CancellationToken.None); + + if (connector != null) + { + // Managed to created a new connector + connector.Connection = null; + + // See increment under over-capacity mode below + Interlocked.Increment(ref connector.CommandsInFlightCount); + + break; + } + + // There were no idle connectors and we're at max capacity, so we can't open a new one. + // Enter over-capacity mode - find an unlocked connector with the least currently in-flight + // commands and sent on it, even though there are already pending commands. + var minInFlight = int.MaxValue; + foreach (var c in Connectors) + { + if (c?.MultiplexAsyncWritingLock == 0 && c.CommandsInFlightCount < minInFlight) + { + minInFlight = c.CommandsInFlightCount; + connector = c; + } + } + + // There could be no writable connectors (all stuck in transaction or flushing). + if (connector == null) + { + // TODO: This is problematic - when absolutely all connectors are both busy *and* currently + // performing (async) I/O, this will spin-wait. + // We could call WaitAsync, but that would wait for an idle connector, whereas we want any + // writeable (non-writing) connector even if it has in-flight commands. Maybe something + // with better back-off. + // On the other hand, this is exactly *one* thread doing spin-wait, maybe not that bad. + spinwait.SpinOnce(); + continue; + } + + // We may be in a race condition with the connector read loop, which may be currently returning + // the connector to the Idle channel (because it has completed all commands). + // Increment the in-flight count to make sure the connector isn't returned as idle. + var newInFlight = Interlocked.Increment(ref connector.CommandsInFlightCount); + if (newInFlight == 1) + { + // The connector's in-flight was 0, so it was idle - abort over-capacity read + // and retry the normal flow. + Interlocked.Decrement(ref connector.CommandsInFlightCount); + spinwait.SpinOnce(); + continue; + } + + break; + } + } + catch (Exception ex) + { + Log.Error("Exception opening a connection", ex); + + // Fail the first command in the channel as a way of bubbling the exception up to the user + command.ExecutionCompletion.SetException(ex); + + continue; + } + + // We now have a ready connector, and can start writing commands to it. + Debug.Assert(connector != null); + + try + { + stats.Reset(); + connector.FlagAsNotWritableForMultiplexing(); + command.TraceCommandStart(connector); + + // Read queued commands and write them to the connector's buffer, for as long as we're + // under our write threshold and timer delay. + // Note we already have one command we read above, and have already updated the connector's + // CommandsInFlightCount. Now write that command. + var writtenSynchronously = WriteCommand(connector, command, ref stats); + + while (connector.WriteBuffer.WritePosition < _writeCoalescingBufferThresholdBytes && + writtenSynchronously && + _multiplexCommandReader.TryRead(out command)) + { + Interlocked.Increment(ref connector.CommandsInFlightCount); + writtenSynchronously = WriteCommand(connector, command, ref stats); + } + + // If all commands were written synchronously (good path), complete the write here, flushing + // and updating statistics. If not, CompleteRewrite is scheduled to run later, when the async + // operations complete, so skip it and continue. + if (writtenSynchronously) + Flush(connector, ref stats); + } + catch (Exception ex) + { + FailWrite(connector, ex); + } + } + + bool WriteCommand(NpgsqlConnector connector, NpgsqlCommand command, ref MultiplexingStats stats) + { + // Note: this method *never* awaits on I/O - doing so would suspend all outgoing multiplexing commands + // for the entire pool. In the normal/fast case, writing the command is purely synchronous (serialize + // to buffer in memory), and the actual flush will occur at the level above. For cases where the + // command overflows the buffer, async I/O is done, and we schedule continuations separately - + // but the main thread continues to handle other commands on other connectors. + if (_autoPrepare) + { + // TODO: Need to log based on numPrepared like in non-multiplexing mode... + var numPrepared = 0; + for (var i = 0; i < command.InternalBatchCommands.Count; i++) + if (command.InternalBatchCommands[i].TryAutoPrepare(connector)) + numPrepared++; + } + + var written = connector.CommandsInFlightWriter!.TryWrite(command); + Debug.Assert(written, $"Failed to enqueue command to {connector.CommandsInFlightWriter}"); + + // Purposefully don't wait for I/O to complete + var task = command.Write(connector, async: true, flush: false); + stats.NumCommands++; + + switch (task.Status) + { + case TaskStatus.RanToCompletion: + return true; + + case TaskStatus.Faulted: + task.GetAwaiter().GetResult(); // Throw the exception + return true; + + case TaskStatus.WaitingForActivation: + case TaskStatus.Running: + { + // Asynchronous completion, which means the writing is flushing to network and there's actual I/O + // (i.e. a big command which overflowed our buffer). + // We don't (ever) await in the write loop, so remove the connector from the writable list (as it's + // still flushing) and schedule a continuation to continue taking care of this connector. + // The write loop continues to the next connector. + + // Create a copy of the statistics and purposefully box it via the closure. We need a separate + // copy of the stats for the async writing that will continue in parallel with this loop. + var clonedStats = stats.Clone(); + + // ReSharper disable once MethodSupportsCancellation + task.ContinueWith((t, o) => + { + var conn = (NpgsqlConnector)o!; + + if (t.IsFaulted) + { + FailWrite(conn, t.Exception!.UnwrapAggregate()); + return; + } + + // There's almost certainly more buffered outgoing data for the command, after the flush + // occured. Complete the write, which will flush again (and update statistics). + try + { + Flush(conn, ref clonedStats); + } + catch (Exception e) + { + FailWrite(conn, e); + } + }, connector); + + return false; + } + + default: + Debug.Fail("When writing command to connector, task is in invalid state " + task.Status); + throw new Exception("When writing command to connector, task is in invalid state " + task.Status); + } + } + + void Flush(NpgsqlConnector connector, ref MultiplexingStats stats) + { + var task = connector.Flush(async: true); + switch (task.Status) + { + case TaskStatus.RanToCompletion: + CompleteWrite(connector, ref stats); + return; + + case TaskStatus.Faulted: + task.GetAwaiter().GetResult(); // Throw the exception + return; + + case TaskStatus.WaitingForActivation: + case TaskStatus.Running: + { + // Asynchronous completion - the flush didn't complete immediately (e.g. TCP zero window). + + // Create a copy of the statistics and purposefully box it via the closure. We need a separate + // copy of the stats for the async writing that will continue in parallel with this loop. + var clonedStats = stats.Clone(); + + task.ContinueWith((t, o) => + { + var conn = (NpgsqlConnector)o!; + if (t.IsFaulted) + { + FailWrite(conn, t.Exception!.UnwrapAggregate()); + return; + } + + CompleteWrite(conn, ref clonedStats); + }, connector); + + return; + } + + default: + Debug.Fail("When flushing, task is in invalid state " + task.Status); + throw new Exception("When flushing, task is in invalid state " + task.Status); + } + } + + void FailWrite(NpgsqlConnector connector, Exception exception) + { + // Note that all commands already passed validation. This means any error here is either an unrecoverable network issue + // (in which case we're already broken), or some other issue while writing (e.g. invalid UTF8 characters in the SQL query) - + // unrecoverable in any case. + + // All commands enqueued in CommandsInFlightWriter will be drained by the reader and failed. + // Note that some of these commands where only written to the connector's buffer, but never + // actually sent - because of a later exception. + // In theory, we could track commands that were only enqueued and not sent, and retry those + // (on another connector), but that would add some book-keeping and complexity, and in any case + // if one connector was broken, chances are that all are (networking). + Debug.Assert(connector.IsBroken); + + Log.Error("Exception while writing commands", exception, connector.Id); + } + + static void CompleteWrite(NpgsqlConnector connector, ref MultiplexingStats stats) + { + // All I/O has completed, mark this connector as safe for writing again. + // This will allow the connector to be returned to the pool by its read loop, and also to be selected + // for over-capacity write. + connector.FlagAsWritableForMultiplexing(); + + NpgsqlEventSource.Log.MultiplexingBatchSent(stats.NumCommands, stats.Stopwatch); + } + + // ReSharper disable once FunctionNeverReturns + } + + public override void Dispose() + { + _bootstrapSemaphore.Dispose(); + base.Dispose(); + } + + struct MultiplexingStats + { + internal Stopwatch Stopwatch; + internal int NumCommands; + + internal void Reset() + { + NumCommands = 0; + Stopwatch.Reset(); + } + + internal MultiplexingStats Clone() + { + var clone = new MultiplexingStats { Stopwatch = Stopwatch, NumCommands = NumCommands }; + Stopwatch = new Stopwatch(); + return clone; + } + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NameTranslation/INpgsqlNameTranslator.cs b/LibExternal/Npgsql/NameTranslation/INpgsqlNameTranslator.cs new file mode 100644 index 0000000..1fa188a --- /dev/null +++ b/LibExternal/Npgsql/NameTranslation/INpgsqlNameTranslator.cs @@ -0,0 +1,19 @@ +namespace Npgsql; + +/// +/// A component which translates a CLR name (e.g. SomeClass) into a database name (e.g. some_class) +/// according to some scheme. +/// Used for mapping enum and composite types. +/// +public interface INpgsqlNameTranslator +{ + /// + /// Given a CLR type name (e.g class, struct, enum), translates its name to a database type name. + /// + string TranslateTypeName(string clrName); + + /// + /// Given a CLR member name (property or field), translates its name to a database type name. + /// + string TranslateMemberName(string clrName); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NameTranslation/NpgsqlNullNameTranslator.cs b/LibExternal/Npgsql/NameTranslation/NpgsqlNullNameTranslator.cs new file mode 100644 index 0000000..30ff5b9 --- /dev/null +++ b/LibExternal/Npgsql/NameTranslation/NpgsqlNullNameTranslator.cs @@ -0,0 +1,19 @@ +using System; + +namespace Npgsql.NameTranslation; + +/// +/// A name translator which preserves CLR names (e.g. SomeClass) when mapping names to the database. +/// +public class NpgsqlNullNameTranslator : INpgsqlNameTranslator +{ + /// + /// Given a CLR type name (e.g class, struct, enum), translates its name to a database type name. + /// + public string TranslateTypeName(string clrName) => clrName ?? throw new ArgumentNullException(nameof(clrName)); + + /// + /// Given a CLR member name (property or field), translates its name to a database type name. + /// + public string TranslateMemberName(string clrName) => clrName ?? throw new ArgumentNullException(nameof(clrName)); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NameTranslation/NpgsqlSnakeCaseNameTranslator.cs b/LibExternal/Npgsql/NameTranslation/NpgsqlSnakeCaseNameTranslator.cs new file mode 100644 index 0000000..963e1f4 --- /dev/null +++ b/LibExternal/Npgsql/NameTranslation/NpgsqlSnakeCaseNameTranslator.cs @@ -0,0 +1,108 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Text; + +namespace Npgsql.NameTranslation; + +/// +/// A name translator which converts standard CLR names (e.g. SomeClass) to snake-case database +/// names (some_class) +/// +public class NpgsqlSnakeCaseNameTranslator : INpgsqlNameTranslator +{ + /// + /// Creates a new . + /// + public NpgsqlSnakeCaseNameTranslator() + : this(false) { } + + /// + /// Creates a new . + /// + /// + /// Uses the legacy naming convention if , otherwise it uses the new naming convention. + /// + public NpgsqlSnakeCaseNameTranslator(bool legacyMode) + => LegacyMode = legacyMode; + + bool LegacyMode { get; } + + /// + /// Given a CLR type name (e.g class, struct, enum), translates its name to a database type name. + /// + public string TranslateTypeName(string clrName) => TranslateMemberName(clrName); + + /// + /// Given a CLR member name (property or field), translates its name to a database type name. + /// + public string TranslateMemberName(string clrName) + { + if (clrName == null) + throw new ArgumentNullException(nameof(clrName)); + + return LegacyMode + ? string.Concat(clrName.Select((c, i) => i > 0 && char.IsUpper(c) ? "_" + c.ToString() : c.ToString())).ToLower() + : ConvertToSnakeCase(clrName); + } + + /// + /// Converts a string to its snake_case equivalent. + /// + /// The value to convert. + public static string ConvertToSnakeCase(string name) + { + if (string.IsNullOrEmpty(name)) + return name; + + var builder = new StringBuilder(name.Length + Math.Min(2, name.Length / 5)); + var previousCategory = default(UnicodeCategory?); + + for (var currentIndex = 0; currentIndex < name.Length; currentIndex++) + { + var currentChar = name[currentIndex]; + if (currentChar == '_') + { + builder.Append('_'); + previousCategory = null; + continue; + } + + var currentCategory = char.GetUnicodeCategory(currentChar); + switch (currentCategory) + { + case UnicodeCategory.UppercaseLetter: + case UnicodeCategory.TitlecaseLetter: + if (previousCategory == UnicodeCategory.SpaceSeparator || + previousCategory == UnicodeCategory.LowercaseLetter || + previousCategory != UnicodeCategory.DecimalDigitNumber && + previousCategory != null && + currentIndex > 0 && + currentIndex + 1 < name.Length && + char.IsLower(name[currentIndex + 1])) + { + builder.Append('_'); + } + + currentChar = char.ToLower(currentChar); + break; + + case UnicodeCategory.LowercaseLetter: + case UnicodeCategory.DecimalDigitNumber: + if (previousCategory == UnicodeCategory.SpaceSeparator) + builder.Append('_'); + break; + + default: + if (previousCategory != null) + previousCategory = UnicodeCategory.SpaceSeparator; + continue; + } + + builder.Append(currentChar); + previousCategory = currentCategory; + } + + return builder.ToString(); + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NoSynchronizationContextScope.cs b/LibExternal/Npgsql/NoSynchronizationContextScope.cs new file mode 100644 index 0000000..d34d884 --- /dev/null +++ b/LibExternal/Npgsql/NoSynchronizationContextScope.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading; + +namespace Npgsql; + +/// +/// This mechanism is used to temporarily set the current synchronization context to null while +/// executing Npgsql code, making all await continuations execute on the thread pool. This replaces +/// the need to place ConfigureAwait(false) everywhere, and should be used in all surface async methods, +/// without exception. +/// +/// Warning: do not use this directly in async methods, use it in sync wrappers of async methods +/// (see https://github.com/npgsql/npgsql/issues/1593) +/// +/// +/// https://stackoverflow.com/a/28307965/640325 +/// +static class NoSynchronizationContextScope +{ + internal static Disposable Enter() => new(SynchronizationContext.Current); + + internal struct Disposable : IDisposable + { + readonly SynchronizationContext? _synchronizationContext; + + internal Disposable(SynchronizationContext? synchronizationContext) + { + if (synchronizationContext != null) + SynchronizationContext.SetSynchronizationContext(null); + + _synchronizationContext = synchronizationContext; + } + + public void Dispose() + => SynchronizationContext.SetSynchronizationContext(_synchronizationContext); + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Npgsql.csproj b/LibExternal/Npgsql/Npgsql.csproj new file mode 100644 index 0000000..448a505 --- /dev/null +++ b/LibExternal/Npgsql/Npgsql.csproj @@ -0,0 +1,11 @@ + + + + net8.0 + enable + enable + true + Debug;Release;Aspire + + + diff --git a/LibExternal/Npgsql/NpgsqlActivitySource.cs b/LibExternal/Npgsql/NpgsqlActivitySource.cs new file mode 100644 index 0000000..002cf4a --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlActivitySource.cs @@ -0,0 +1,88 @@ +using Npgsql.Internal; +using System; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Reflection; + +namespace Npgsql; + +static class NpgsqlActivitySource +{ + static readonly ActivitySource Source; + + static NpgsqlActivitySource() + { + var assembly = typeof(NpgsqlActivitySource).Assembly; + var version = assembly.GetCustomAttribute()?.Version ?? "0.0.0"; + Source = new("Npgsql", version); + } + + internal static bool IsEnabled => Source.HasListeners(); + + internal static Activity? CommandStart(NpgsqlConnector connector, string sql) + { + var settings = connector.Settings; + var activity = Source.StartActivity(settings.Database!, ActivityKind.Client); + if (activity is not { IsAllDataRequested: true }) + return activity; + + activity.SetTag("db.system", "postgresql"); + activity.SetTag("db.connection_string", connector.UserFacingConnectionString); + activity.SetTag("db.user", settings.Username); + activity.SetTag("db.name", settings.Database); + activity.SetTag("db.statement", sql); + activity.SetTag("db.connection_id", connector.Id); + + var endPoint = connector.ConnectedEndPoint; + Debug.Assert(endPoint is not null); + switch (endPoint) + { + case IPEndPoint ipEndPoint: + activity.SetTag("net.transport", "ip_tcp"); + activity.SetTag("net.peer.ip", ipEndPoint.Address.ToString()); + if (ipEndPoint.Port != 5432) + activity.SetTag("net.peer.port", ipEndPoint.Port); + activity.SetTag("net.peer.name", settings.Host); + break; + + case UnixDomainSocketEndPoint: + activity.SetTag("net.transport", "unix"); + activity.SetTag("net.peer.name", settings.Host); + break; + + default: + throw new ArgumentOutOfRangeException("Invalid endpoint type: " + endPoint.GetType()); + } + + return activity; + } + + internal static void ReceivedFirstResponse(Activity activity) + { + var activityEvent = new ActivityEvent("received-first-response"); + activity.AddEvent(activityEvent); + } + + internal static void CommandStop(Activity activity) + { + activity.SetTag("otel.status_code", "OK"); + activity.Dispose(); + } + + internal static void SetException(Activity activity, Exception ex, bool escaped = true) + { + var tags = new ActivityTagsCollection + { + { "exception.type", ex.GetType().FullName }, + { "exception.message", ex.Message }, + { "exception.stacktrace", ex.ToString() }, + { "exception.escaped", escaped } + }; + var activityEvent = new ActivityEvent("exception", tags: tags); + activity.AddEvent(activityEvent); + activity.SetTag("otel.status_code", "ERROR"); + activity.SetTag("otel.status_description", ex is PostgresException pgEx ? pgEx.SqlState : ex.Message); + activity.Dispose(); + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlBatch.cs b/LibExternal/Npgsql/NpgsqlBatch.cs new file mode 100644 index 0000000..ea54b2e --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlBatch.cs @@ -0,0 +1,143 @@ +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.Internal; + +namespace Npgsql; + +/// +public class NpgsqlBatch : DbBatch +{ + readonly NpgsqlCommand _command; + + /// + protected override DbBatchCommandCollection DbBatchCommands => BatchCommands; + + /// + public new NpgsqlBatchCommandCollection BatchCommands { get; } + + /// + public override int Timeout + { + get => _command.CommandTimeout; + set => _command.CommandTimeout = value; + } + + /// + public new NpgsqlConnection? Connection + { + get => _command.Connection; + set => _command.Connection = value; + } + + /// + protected override DbConnection? DbConnection + { + get => Connection; + set => Connection = (NpgsqlConnection?)value; + } + + /// + public new NpgsqlTransaction? Transaction + { + get => _command.Transaction; + set => _command.Transaction = value; + } + + /// + protected override DbTransaction? DbTransaction + { + get => Transaction; + set => Transaction = (NpgsqlTransaction?)value; + } + + /// + /// Marks all of the batch's result columns as either known or unknown. + /// Unknown results column are requested them from PostgreSQL in text format, and Npgsql makes no + /// attempt to parse them. They will be accessible as strings only. + /// + internal bool AllResultTypesAreUnknown + { + get => _command.AllResultTypesAreUnknown; + set => _command.AllResultTypesAreUnknown = value; + } + + /// + /// Initializes a new . + /// + /// A that represents the connection to a PostgreSQL server. + /// The in which the executes. + public NpgsqlBatch(NpgsqlConnection? connection = null, NpgsqlTransaction? transaction = null) + { + var batchCommands = new List(5); + _command = new(batchCommands); + BatchCommands = new NpgsqlBatchCommandCollection(batchCommands); + + Connection = connection; + Transaction = transaction; + } + + internal NpgsqlBatch(NpgsqlConnector connector) + { + var batchCommands = new List(5); + _command = new(connector, batchCommands); + BatchCommands = new NpgsqlBatchCommandCollection(batchCommands); + } + + /// + protected override DbBatchCommand CreateDbBatchCommand() + => new NpgsqlBatchCommand(); + + /// + protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) + => ExecuteReader(behavior); + + /// + public new NpgsqlDataReader ExecuteReader(CommandBehavior behavior = CommandBehavior.Default) + => _command.ExecuteReader(behavior); + + /// + protected override async Task ExecuteDbDataReaderAsync( + CommandBehavior behavior, + CancellationToken cancellationToken) + => await ExecuteReaderAsync(behavior, cancellationToken); + + /// + public new Task ExecuteReaderAsync(CancellationToken cancellationToken = default) + => _command.ExecuteReaderAsync(cancellationToken); + + /// + public new Task ExecuteReaderAsync( + CommandBehavior behavior, + CancellationToken cancellationToken = default) + => _command.ExecuteReaderAsync(behavior, cancellationToken); + + /// + public override int ExecuteNonQuery() + => _command.ExecuteNonQuery(); + + /// + public override Task ExecuteNonQueryAsync(CancellationToken cancellationToken = default) + => _command.ExecuteNonQueryAsync(cancellationToken); + + /// + public override object? ExecuteScalar() + => _command.ExecuteScalar(); + + /// + public override Task ExecuteScalarAsync(CancellationToken cancellationToken = default) + => _command.ExecuteScalarAsync(cancellationToken); + + /// + public override void Prepare() + => _command.Prepare(); + + /// + public override Task PrepareAsync(CancellationToken cancellationToken = default) + => _command.PrepareAsync(cancellationToken); + + /// + public override void Cancel() => _command.Cancel(); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlBatchCommand.cs b/LibExternal/Npgsql/NpgsqlBatchCommand.cs new file mode 100644 index 0000000..66ac63f --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlBatchCommand.cs @@ -0,0 +1,243 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using Npgsql.BackendMessages; +using Npgsql.Internal; + +namespace Npgsql; + +/// +public sealed class NpgsqlBatchCommand : DbBatchCommand +{ + string _commandText; + + /// + [AllowNull] + public override string CommandText + { + get => _commandText; + set + { + _commandText = value ?? string.Empty; + + ResetPreparation(); + // TODO: Technically should do this also if the parameter list (or type) changes + } + } + + /// + public override CommandType CommandType { get; set; } = CommandType.Text; + + /// + protected override DbParameterCollection DbParameterCollection => Parameters; + + /// + public new NpgsqlParameterCollection Parameters { get; } = new(); + + /// + /// The number of rows affected or retrieved. + /// + /// + /// See the command tag in the CommandComplete message for the meaning of this value for each , + /// https://www.postgresql.org/docs/current/static/protocol-message-formats.html + /// + public ulong Rows { get; internal set; } + + /// + public override int RecordsAffected + { + get + { + switch (StatementType) + { + case StatementType.Update: + case StatementType.Insert: + case StatementType.Delete: + case StatementType.Copy: + case StatementType.Move: + return Rows > int.MaxValue + ? throw new OverflowException($"The number of records affected exceeds int.MaxValue. Use {nameof(Rows)}.") + : (int)Rows; + default: + return -1; + } + } + } + + /// + /// Specifies the type of query, e.g. SELECT. + /// + public StatementType StatementType { get; internal set; } + + /// + /// For an INSERT, the object ID of the inserted row if is 1 and + /// the target table has OIDs; otherwise 0. + /// + public uint OID { get; internal set; } + + /// + /// The SQL as it will be sent to PostgreSQL, after any rewriting performed by Npgsql (e.g. named to positional parameter + /// placeholders). + /// + internal string? FinalCommandText { get; set; } + + /// + /// The list of parameters, ordered positionally, as it will be sent to PostgreSQL. + /// + /// + /// If the user provided positional parameters, this references the (in batching mode) or the list + /// backing (in non-batching) mode. If the user provided named parameters, this is a + /// separate list containing the re-ordered parameters. + /// + internal List PositionalParameters + { + get => _inputParameters ??= _ownedInputParameters ??= new(); + set => _inputParameters = value; + } + + List? _ownedInputParameters; + List? _inputParameters; + + /// + /// The RowDescription message for this query. If null, the query does not return rows (e.g. INSERT) + /// + internal RowDescriptionMessage? Description + { + get => PreparedStatement == null ? _description : PreparedStatement.Description; + set + { + if (PreparedStatement == null) + _description = value; + else + PreparedStatement.Description = value; + } + } + + RowDescriptionMessage? _description; + + /// + /// If this statement has been automatically prepared, references the . + /// Null otherwise. + /// + internal PreparedStatement? PreparedStatement + { + get => _preparedStatement != null && _preparedStatement.State == PreparedState.Unprepared + ? _preparedStatement = null + : _preparedStatement; + set => _preparedStatement = value; + } + + PreparedStatement? _preparedStatement; + + internal NpgsqlConnector? ConnectorPreparedOn { get; set; } + + internal bool IsPreparing; + + /// + /// Holds the server-side (prepared) statement name. Empty string for non-prepared statements. + /// + internal string StatementName => PreparedStatement?.Name ?? ""; + + /// + /// Whether this statement has already been prepared (including automatic preparation). + /// + internal bool IsPrepared => PreparedStatement?.IsPrepared == true; + + /// + /// Returns a prepared statement for this statement (including automatic preparation). + /// + internal bool TryGetPrepared([NotNullWhen(true)] out PreparedStatement? preparedStatement) + { + preparedStatement = PreparedStatement; + return preparedStatement?.IsPrepared == true; + } + + /// + /// Initializes a new . + /// + public NpgsqlBatchCommand() : this(string.Empty) {} + + /// + /// Initializes a new . + /// + /// The text of the . + public NpgsqlBatchCommand(string commandText) + => _commandText = commandText; + + internal bool ExplicitPrepare(NpgsqlConnector connector) + { + if (!IsPrepared) + { + PreparedStatement = connector.PreparedStatementManager.GetOrAddExplicit(this); + + if (PreparedStatement?.State == PreparedState.NotPrepared) + { + PreparedStatement.State = PreparedState.BeingPrepared; + IsPreparing = true; + return true; + } + } + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryAutoPrepare(NpgsqlConnector connector) + { + // If this statement isn't prepared, see if it gets implicitly prepared. + // Note that this may return null (not enough usages for automatic preparation). + if (!TryGetPrepared(out var preparedStatement)) + preparedStatement = PreparedStatement = connector.PreparedStatementManager.TryGetAutoPrepared(this); + if (preparedStatement is not null) + { + if (preparedStatement.State == PreparedState.NotPrepared) + { + preparedStatement.State = PreparedState.BeingPrepared; + IsPreparing = true; + } + + return true; + } + + return false; + } + + internal void Reset() + { + CommandText = string.Empty; + StatementType = StatementType.Select; + _description = null; + Rows = 0; + OID = 0; + PreparedStatement = null; + + if (ReferenceEquals(_inputParameters, _ownedInputParameters)) + PositionalParameters.Clear(); + else if (_inputParameters is not null) + _inputParameters = null; // We're pointing at a user's NpgsqlParameterCollection + Debug.Assert(_inputParameters is null || _inputParameters.Count == 0); + Debug.Assert(_ownedInputParameters is null || _ownedInputParameters.Count == 0); + } + + internal void ApplyCommandComplete(CommandCompleteMessage msg) + { + StatementType = msg.StatementType; + Rows = msg.Rows; + OID = msg.OID; + } + + internal void ResetPreparation() + { + PreparedStatement = null; + ConnectorPreparedOn = null; + } + + /// + /// Returns the . + /// + public override string ToString() => CommandText; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlBatchCommandCollection.cs b/LibExternal/Npgsql/NpgsqlBatchCommandCollection.cs new file mode 100644 index 0000000..7a345f6 --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlBatchCommandCollection.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Data.Common; + +namespace Npgsql; + +/// +public class NpgsqlBatchCommandCollection : DbBatchCommandCollection, IList +{ + readonly List _list; + + internal NpgsqlBatchCommandCollection(List batchCommands) + => _list = batchCommands; + + /// + public override int Count => _list.Count; + + /// + public override bool IsReadOnly => false; + + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + + /// + public override IEnumerator GetEnumerator() => _list.GetEnumerator(); + + /// + public void Add(NpgsqlBatchCommand item) => _list.Add(item); + + /// + public override void Add(DbBatchCommand item) => Add(Cast(item)); + + /// + public override void Clear() => _list.Clear(); + + /// + public bool Contains(NpgsqlBatchCommand item) => _list.Contains(item); + + /// + public override bool Contains(DbBatchCommand item) => Contains(Cast(item)); + + /// + public void CopyTo(NpgsqlBatchCommand[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex); + + /// + public override void CopyTo(DbBatchCommand[] array, int arrayIndex) + { + if (array is NpgsqlBatchCommand[] typedArray) + { + CopyTo(typedArray, arrayIndex); + return; + } + + throw new InvalidCastException( + $"{nameof(array)} is not of type {nameof(NpgsqlBatchCommand)} and cannot be used in this batch command collection."); + } + + /// + public int IndexOf(NpgsqlBatchCommand item) => _list.IndexOf(item); + + /// + public override int IndexOf(DbBatchCommand item) => IndexOf(Cast(item)); + + /// + public void Insert(int index, NpgsqlBatchCommand item) => _list.Insert(index, item); + + /// + public override void Insert(int index, DbBatchCommand item) => Insert(index, Cast(item)); + + /// + public bool Remove(NpgsqlBatchCommand item) => _list.Remove(item); + + /// + public override bool Remove(DbBatchCommand item) => Remove(Cast(item)); + + /// + public override void RemoveAt(int index) => _list.RemoveAt(index); + + NpgsqlBatchCommand IList.this[int index] + { + get => _list[index]; + set => _list[index] = value; + } + + /// + public new NpgsqlBatchCommand this[int index] + { + get => _list[index]; + set => _list[index] = value; + } + + /// + protected override DbBatchCommand GetBatchCommand(int index) + => _list[index]; + + /// + protected override void SetBatchCommand(int index, DbBatchCommand batchCommand) + => _list[index] = Cast(batchCommand); + + static NpgsqlBatchCommand Cast(DbBatchCommand? value) + => value is NpgsqlBatchCommand c + ? c + : throw new InvalidCastException( + $"The value \"{value}\" is not of type \"{nameof(NpgsqlBatchCommand)}\" and cannot be used in this batch command collection."); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlBinaryExporter.cs b/LibExternal/Npgsql/NpgsqlBinaryExporter.cs new file mode 100644 index 0000000..fda7fdf --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlBinaryExporter.cs @@ -0,0 +1,460 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.Internal; +using Npgsql.Internal.TypeHandling; +using Npgsql.Logging; +using Npgsql.TypeMapping; +using NpgsqlTypes; +using static Npgsql.Util.Statics; + +namespace Npgsql; + +/// +/// Provides an API for a binary COPY TO operation, a high-performance data export mechanism from +/// a PostgreSQL table. Initiated by +/// +public sealed class NpgsqlBinaryExporter : ICancelable +{ + #region Fields and Properties + + NpgsqlConnector _connector; + NpgsqlReadBuffer _buf; + ConnectorTypeMapper _typeMapper; + bool _isConsumed, _isDisposed; + int _leftToReadInDataMsg, _columnLen; + + short _column; + + /// + /// The number of columns, as returned from the backend in the CopyInResponse. + /// + internal int NumColumns { get; private set; } + + NpgsqlTypeHandler?[] _typeHandlerCache; + + static readonly NpgsqlLogger Log = NpgsqlLogManager.CreateLogger(nameof(NpgsqlBinaryExporter)); + + /// + /// Current timeout + /// + public TimeSpan Timeout + { + set + { + _buf.Timeout = value; + // While calling Complete(), we're using the connector, which overwrites the buffer's timeout with it's own + _connector.UserTimeout = (int)value.TotalMilliseconds; + } + } + + #endregion + + #region Construction / Initialization + + internal NpgsqlBinaryExporter(NpgsqlConnector connector) + { + _connector = connector; + _buf = connector.ReadBuffer; + _typeMapper = connector.TypeMapper; + _columnLen = int.MinValue; // Mark that the (first) column length hasn't been read yet + _column = -1; + _typeHandlerCache = null!; + } + + internal async Task Init(string copyToCommand, bool async, CancellationToken cancellationToken = default) + { + await _connector.WriteQuery(copyToCommand, async, cancellationToken); + await _connector.Flush(async, cancellationToken); + + using var registration = _connector.StartNestedCancellableOperation(cancellationToken, attemptPgCancellation: false); + + CopyOutResponseMessage copyOutResponse; + var msg = await _connector.ReadMessage(async); + switch (msg.Code) + { + case BackendMessageCode.CopyOutResponse: + copyOutResponse = (CopyOutResponseMessage) msg; + if (!copyOutResponse.IsBinary) + { + throw _connector.Break( + new ArgumentException("copyToCommand triggered a text transfer, only binary is allowed", + nameof(copyToCommand))); + } + break; + case BackendMessageCode.CommandComplete: + throw new InvalidOperationException( + "This API only supports import/export from the client, i.e. COPY commands containing TO/FROM STDIN. " + + "To import/export with files on your PostgreSQL machine, simply execute the command with ExecuteNonQuery. " + + "Note that your data has been successfully imported/exported."); + default: + throw _connector.UnexpectedMessageReceived(msg.Code); + } + + NumColumns = copyOutResponse.NumColumns; + _typeHandlerCache = new NpgsqlTypeHandler[NumColumns]; + await ReadHeader(async); + } + + async Task ReadHeader(bool async) + { + _leftToReadInDataMsg = Expect(await _connector.ReadMessage(async), _connector).Length; + var headerLen = NpgsqlRawCopyStream.BinarySignature.Length + 4 + 4; + await _buf.Ensure(headerLen, async); + + if (NpgsqlRawCopyStream.BinarySignature.Any(t => _buf.ReadByte() != t)) + throw new NpgsqlException("Invalid COPY binary signature at beginning!"); + + var flags = _buf.ReadInt32(); + if (flags != 0) + throw new NotSupportedException("Unsupported flags in COPY operation (OID inclusion?)"); + + _buf.ReadInt32(); // Header extensions, currently unused + _leftToReadInDataMsg -= headerLen; + } + + #endregion + + #region Read + + /// + /// Starts reading a single row, must be invoked before reading any columns. + /// + /// + /// The number of columns in the row. -1 if there are no further rows. + /// Note: This will currently be the same value for all rows, but this may change in the future. + /// + public int StartRow() => StartRow(false).GetAwaiter().GetResult(); + + /// + /// Starts reading a single row, must be invoked before reading any columns. + /// + /// + /// The number of columns in the row. -1 if there are no further rows. + /// Note: This will currently be the same value for all rows, but this may change in the future. + /// + public ValueTask StartRowAsync(CancellationToken cancellationToken = default) + { + using (NoSynchronizationContextScope.Enter()) + return StartRow(true, cancellationToken); + } + + async ValueTask StartRow(bool async, CancellationToken cancellationToken = default) + { + CheckDisposed(); + if (_isConsumed) + return -1; + + using var registration = _connector.StartNestedCancellableOperation(cancellationToken); + + // The very first row (i.e. _column == -1) is included in the header's CopyData message. + // Otherwise we need to read in a new CopyData row (the docs specify that there's a CopyData + // message per row). + if (_column == NumColumns) + _leftToReadInDataMsg = Expect(await _connector.ReadMessage(async), _connector).Length; + else if (_column != -1) + throw new InvalidOperationException("Already in the middle of a row"); + + await _buf.Ensure(2, async); + _leftToReadInDataMsg -= 2; + + var numColumns = _buf.ReadInt16(); + if (numColumns == -1) + { + Debug.Assert(_leftToReadInDataMsg == 0); + Expect(await _connector.ReadMessage(async), _connector); + Expect(await _connector.ReadMessage(async), _connector); + Expect(await _connector.ReadMessage(async), _connector); + _column = -1; + _isConsumed = true; + return -1; + } + + Debug.Assert(numColumns == NumColumns); + + _column = 0; + return NumColumns; + } + + /// + /// Reads the current column, returns its value and moves ahead to the next column. + /// If the column is null an exception is thrown. + /// + /// + /// The type of the column to be read. This must correspond to the actual type or data + /// corruption will occur. If in doubt, use to manually + /// specify the type. + /// + /// The value of the column + public T Read() => Read(false).GetAwaiter().GetResult(); + + /// + /// Reads the current column, returns its value and moves ahead to the next column. + /// If the column is null an exception is thrown. + /// + /// + /// The type of the column to be read. This must correspond to the actual type or data + /// corruption will occur. If in doubt, use to manually + /// specify the type. + /// + /// The value of the column + public ValueTask ReadAsync(CancellationToken cancellationToken = default) + { + using (NoSynchronizationContextScope.Enter()) + return Read(true, cancellationToken); + } + + ValueTask Read(bool async, CancellationToken cancellationToken = default) + { + CheckDisposed(); + + if (_column == -1 || _column == NumColumns) + throw new InvalidOperationException("Not reading a row"); + + var type = typeof(T); + var handler = _typeHandlerCache[_column]; + if (handler == null) + handler = _typeHandlerCache[_column] = _typeMapper.ResolveByClrType(type); + + return DoRead(handler, async, cancellationToken); + } + + /// + /// Reads the current column, returns its value according to and + /// moves ahead to the next column. + /// If the column is null an exception is thrown. + /// + /// + /// In some cases isn't enough to infer the data type coming in from the + /// database. This parameter can be used to unambiguously specify the type. An example is the JSONB + /// type, for which will be a simple string but for which + /// must be specified as . + /// + /// The .NET type of the column to be read. + /// The value of the column + public T Read(NpgsqlDbType type) => Read(type, false).GetAwaiter().GetResult(); + + /// + /// Reads the current column, returns its value according to and + /// moves ahead to the next column. + /// If the column is null an exception is thrown. + /// + /// + /// In some cases isn't enough to infer the data type coming in from the + /// database. This parameter can be used to unambiguously specify the type. An example is the JSONB + /// type, for which will be a simple string but for which + /// must be specified as . + /// + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// The .NET type of the column to be read. + /// The value of the column + public ValueTask ReadAsync(NpgsqlDbType type, CancellationToken cancellationToken = default) + { + using (NoSynchronizationContextScope.Enter()) + return Read(type, true, cancellationToken); + } + + ValueTask Read(NpgsqlDbType type, bool async, CancellationToken cancellationToken = default) + { + CheckDisposed(); + if (_column == -1 || _column == NumColumns) + throw new InvalidOperationException("Not reading a row"); + + var handler = _typeHandlerCache[_column]; + if (handler == null) + handler = _typeHandlerCache[_column] = _typeMapper.ResolveByNpgsqlDbType(type); + + return DoRead(handler, async, cancellationToken); + } + + async ValueTask DoRead(NpgsqlTypeHandler handler, bool async, CancellationToken cancellationToken = default) + { + try + { + using var registration = _connector.StartNestedCancellableOperation(cancellationToken); + + await ReadColumnLenIfNeeded(async); + + if (_columnLen == -1) + { +#pragma warning disable CS8653 // A default expression introduces a null value when 'T' is a non-nullable reference type. + // When T is a Nullable, we support returning null + if (NullableHandler.Exists) + return default!; +#pragma warning restore CS8653 + throw new InvalidCastException("Column is null"); + } + + // If we know the entire column is already in memory, use the code path without async + var result = NullableHandler.Exists + ? _columnLen <= _buf.ReadBytesLeft + ? NullableHandler.Read(handler, _buf, _columnLen) + : await NullableHandler.ReadAsync(handler, _buf, _columnLen, async) + : _columnLen <= _buf.ReadBytesLeft + ? handler.Read(_buf, _columnLen) + : await handler.Read(_buf, _columnLen, async); + + _leftToReadInDataMsg -= _columnLen; + _columnLen = int.MinValue; // Mark that the (next) column length hasn't been read yet + _column++; + return result; + } + catch (Exception e) + { + _connector.Break(e); + throw; + } + } + + /// + /// Returns whether the current column is null. + /// + public bool IsNull + { + get + { + ReadColumnLenIfNeeded(false).GetAwaiter().GetResult(); + return _columnLen == -1; + } + } + + /// + /// Skips the current column without interpreting its value. + /// + public void Skip() => Skip(false).GetAwaiter().GetResult(); + + /// + /// Skips the current column without interpreting its value. + /// + public Task SkipAsync(CancellationToken cancellationToken = default) + { + using (NoSynchronizationContextScope.Enter()) + return Skip(true, cancellationToken); + } + + async Task Skip(bool async, CancellationToken cancellationToken = default) + { + CheckDisposed(); + + using var registration = _connector.StartNestedCancellableOperation(cancellationToken); + + await ReadColumnLenIfNeeded(async); + if (_columnLen != -1) + await _buf.Skip(_columnLen, async); + + _columnLen = int.MinValue; + _column++; + } + + #endregion + + #region Utilities + + async Task ReadColumnLenIfNeeded(bool async) + { + if (_columnLen == int.MinValue) + { + await _buf.Ensure(4, async); + _columnLen = _buf.ReadInt32(); + _leftToReadInDataMsg -= 4; + } + } + + void CheckDisposed() + { + if (_isDisposed) + throw new ObjectDisposedException(GetType().FullName, "The COPY operation has already ended."); + } + + #endregion + + #region Cancel / Close / Dispose + + /// + /// Cancels an ongoing export. + /// + public void Cancel() => _connector.PerformUserCancellation(); + + /// + /// Async cancels an ongoing export. + /// + public Task CancelAsync() + { + Cancel(); + return Task.CompletedTask; + } + + /// + /// Completes that binary export and sets the connection back to idle state + /// + public void Dispose() => DisposeAsync(false).GetAwaiter().GetResult(); + + /// + /// Async completes that binary export and sets the connection back to idle state + /// + /// + public ValueTask DisposeAsync() + { + using (NoSynchronizationContextScope.Enter()) + return DisposeAsync(true); + } + + async ValueTask DisposeAsync(bool async) + { + if (_isDisposed) + return; + + if (!_isConsumed && !_connector.IsBroken) + { + try + { + using var registration = _connector.StartNestedCancellableOperation(attemptPgCancellation: false); + // Finish the current CopyData message + _buf.Skip(_leftToReadInDataMsg); + // Read to the end + _connector.SkipUntil(BackendMessageCode.CopyDone); + // We intentionally do not pass a CancellationToken since we don't want to cancel cleanup + Expect(await _connector.ReadMessage(async), _connector); + Expect(await _connector.ReadMessage(async), _connector); + } + catch (OperationCanceledException e) when (e.InnerException is PostgresException pg && pg.SqlState == PostgresErrorCodes.QueryCanceled) + { + Log.Debug($"Caught an exception while disposing the {nameof(NpgsqlBinaryExporter)}, indicating that it was cancelled.", e, _connector.Id); + } + catch (Exception e) + { + Log.Error($"Caught an exception while disposing the {nameof(NpgsqlBinaryExporter)}.", e, _connector.Id); + } + } + + _connector.EndUserAction(); + Cleanup(); + } + +#pragma warning disable CS8625 + void Cleanup() + { + Debug.Assert(!_isDisposed); + var connector = _connector; + Log.Debug("COPY operation ended", connector?.Id ?? -1); + + if (connector != null) + { + connector.CurrentCopyOperation = null; + _connector.Connection?.EndBindingScope(ConnectorBindingScope.Copy); + _connector = null; + } + + _typeMapper = null; + _buf = null; + _isDisposed = true; + } +#pragma warning restore CS8625 + + #endregion +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlBinaryImporter.cs b/LibExternal/Npgsql/NpgsqlBinaryImporter.cs new file mode 100644 index 0000000..139b0bc --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlBinaryImporter.cs @@ -0,0 +1,611 @@ +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.Internal; +using Npgsql.Logging; +using NpgsqlTypes; +using static Npgsql.Util.Statics; + +namespace Npgsql; + +/// +/// Provides an API for a binary COPY FROM operation, a high-performance data import mechanism to +/// a PostgreSQL table. Initiated by +/// +/// +/// See https://www.postgresql.org/docs/current/static/sql-copy.html. +/// +public sealed class NpgsqlBinaryImporter : ICancelable +{ + #region Fields and Properties + + NpgsqlConnector _connector; + NpgsqlWriteBuffer _buf; + + ImporterState _state; + + /// + /// The number of columns in the current (not-yet-written) row. + /// + short _column; + + /// + /// The number of columns, as returned from the backend in the CopyInResponse. + /// + internal int NumColumns { get; private set; } + + bool InMiddleOfRow => _column != -1 && _column != NumColumns; + + NpgsqlParameter?[] _params; + + static readonly NpgsqlLogger Log = NpgsqlLogManager.CreateLogger(nameof(NpgsqlBinaryImporter)); + + /// + /// Current timeout + /// + public TimeSpan Timeout + { + set + { + _buf.Timeout = value; + // While calling Complete(), we're using the connector, which overwrites the buffer's timeout with it's own + _connector.UserTimeout = (int)value.TotalMilliseconds; + } + } + + #endregion + + #region Construction / Initialization + + internal NpgsqlBinaryImporter(NpgsqlConnector connector) + { + _connector = connector; + _buf = connector.WriteBuffer; + _column = -1; + _params = null!; + } + + internal async Task Init(string copyFromCommand, bool async, CancellationToken cancellationToken = default) + { + await _connector.WriteQuery(copyFromCommand, async, cancellationToken); + await _connector.Flush(async, cancellationToken); + + using var registration = _connector.StartNestedCancellableOperation(cancellationToken, attemptPgCancellation: false); + + CopyInResponseMessage copyInResponse; + var msg = await _connector.ReadMessage(async); + switch (msg.Code) + { + case BackendMessageCode.CopyInResponse: + copyInResponse = (CopyInResponseMessage) msg; + if (!copyInResponse.IsBinary) + { + throw _connector.Break( + new ArgumentException("copyFromCommand triggered a text transfer, only binary is allowed", + nameof(copyFromCommand))); + } + break; + case BackendMessageCode.CommandComplete: + throw new InvalidOperationException( + "This API only supports import/export from the client, i.e. COPY commands containing TO/FROM STDIN. " + + "To import/export with files on your PostgreSQL machine, simply execute the command with ExecuteNonQuery. " + + "Note that your data has been successfully imported/exported."); + default: + throw _connector.UnexpectedMessageReceived(msg.Code); + } + + NumColumns = copyInResponse.NumColumns; + _params = new NpgsqlParameter[NumColumns]; + _buf.StartCopyMode(); + WriteHeader(); + } + + void WriteHeader() + { + _buf.WriteBytes(NpgsqlRawCopyStream.BinarySignature, 0, NpgsqlRawCopyStream.BinarySignature.Length); + _buf.WriteInt32(0); // Flags field. OID inclusion not supported at the moment. + _buf.WriteInt32(0); // Header extension area length + } + + #endregion + + #region Write + + /// + /// Starts writing a single row, must be invoked before writing any columns. + /// + public void StartRow() => StartRow(false).GetAwaiter().GetResult(); + + /// + /// Starts writing a single row, must be invoked before writing any columns. + /// + public Task StartRowAsync(CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + using (NoSynchronizationContextScope.Enter()) + return StartRow(true, cancellationToken); + } + + async Task StartRow(bool async, CancellationToken cancellationToken = default) + { + CheckReady(); + + if (_column != -1 && _column != NumColumns) + ThrowHelper.ThrowInvalidOperationException_BinaryImportParametersMismatch(NumColumns, _column); + + if (_buf.WriteSpaceLeft < 2) + await _buf.Flush(async, cancellationToken); + _buf.WriteInt16(NumColumns); + + _column = 0; + } + + /// + /// Writes a single column in the current row. + /// + /// The value to be written + /// + /// The type of the column to be written. This must correspond to the actual type or data + /// corruption will occur. If in doubt, use to manually + /// specify the type. + /// + public void Write([AllowNull] T value) => Write(value, false).GetAwaiter().GetResult(); + + /// + /// Writes a single column in the current row. + /// + /// The value to be written + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// + /// The type of the column to be written. This must correspond to the actual type or data + /// corruption will occur. If in doubt, use to manually + /// specify the type. + /// + public Task WriteAsync([AllowNull] T value, CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + using (NoSynchronizationContextScope.Enter()) + return Write(value, true, cancellationToken); + } + + Task Write([AllowNull] T value, bool async, CancellationToken cancellationToken = default) + { + CheckColumnIndex(); + + var p = _params[_column]; + if (p == null) + { + // First row, create the parameter objects + _params[_column] = p = typeof(T) == typeof(object) + ? new NpgsqlParameter() + : new NpgsqlParameter(); + } + + return Write(value, p, async, cancellationToken); + } + + /// + /// Writes a single column in the current row as type . + /// + /// The value to be written + /// + /// In some cases isn't enough to infer the data type to be written to + /// the database. This parameter can be used to unambiguously specify the type. An example is + /// the JSONB type, for which will be a simple string but for which + /// must be specified as . + /// + /// The .NET type of the column to be written. + public void Write([AllowNull] T value, NpgsqlDbType npgsqlDbType) => + Write(value, npgsqlDbType, false).GetAwaiter().GetResult(); + + /// + /// Writes a single column in the current row as type . + /// + /// The value to be written + /// + /// In some cases isn't enough to infer the data type to be written to + /// the database. This parameter can be used to unambiguously specify the type. An example is + /// the JSONB type, for which will be a simple string but for which + /// must be specified as . + /// + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// The .NET type of the column to be written. + public Task WriteAsync([AllowNull] T value, NpgsqlDbType npgsqlDbType, CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + using (NoSynchronizationContextScope.Enter()) + return Write(value, npgsqlDbType, true, cancellationToken); + } + + Task Write([AllowNull] T value, NpgsqlDbType npgsqlDbType, bool async, CancellationToken cancellationToken = default) + { + CheckColumnIndex(); + + var p = _params[_column]; + if (p == null) + { + // First row, create the parameter objects + _params[_column] = p = typeof(T) == typeof(object) + ? new NpgsqlParameter() + : new NpgsqlParameter(); + p.NpgsqlDbType = npgsqlDbType; + } + + if (npgsqlDbType != p.NpgsqlDbType) + throw new InvalidOperationException($"Can't change {nameof(p.NpgsqlDbType)} from {p.NpgsqlDbType} to {npgsqlDbType}"); + + return Write(value, p, async, cancellationToken); + } + + /// + /// Writes a single column in the current row as type . + /// + /// The value to be written + /// + /// In some cases isn't enough to infer the data type to be written to + /// the database. This parameter and be used to unambiguously specify the type. + /// + /// The .NET type of the column to be written. + public void Write([AllowNull] T value, string dataTypeName) => + Write(value, dataTypeName, false).GetAwaiter().GetResult(); + + /// + /// Writes a single column in the current row as type . + /// + /// The value to be written + /// + /// In some cases isn't enough to infer the data type to be written to + /// the database. This parameter and be used to unambiguously specify the type. + /// + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// The .NET type of the column to be written. + public Task WriteAsync([AllowNull] T value, string dataTypeName, CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + using (NoSynchronizationContextScope.Enter()) + return Write(value, dataTypeName, true, cancellationToken); + } + + Task Write([AllowNull] T value, string dataTypeName, bool async, CancellationToken cancellationToken = default) + { + CheckColumnIndex(); + + var p = _params[_column]; + if (p == null) + { + // First row, create the parameter objects + _params[_column] = p = typeof(T) == typeof(object) + ? new NpgsqlParameter() + : new NpgsqlParameter(); + p.DataTypeName = dataTypeName; + } + + //if (dataTypeName!= p.DataTypeName) + // throw new InvalidOperationException($"Can't change {nameof(p.DataTypeName)} from {p.DataTypeName} to {dataTypeName}"); + + return Write(value, p, async, cancellationToken); + } + + async Task Write([AllowNull] T value, NpgsqlParameter param, bool async, CancellationToken cancellationToken = default) + { + CheckReady(); + if (_column == -1) + throw new InvalidOperationException("A row hasn't been started"); + + if (value == null || value is DBNull) + { + await WriteNull(async, cancellationToken); + return; + } + + if (typeof(T) == typeof(object)) + { + param.Value = value; + } + else + { + if (param is not NpgsqlParameter typedParam) + { + _params[_column] = typedParam = new NpgsqlParameter(); + typedParam.NpgsqlDbType = param.NpgsqlDbType; + param = typedParam; + } + typedParam.TypedValue = value; + } + param.ResolveHandler(_connector.TypeMapper); + param.ValidateAndGetLength(); + param.LengthCache?.Rewind(); + await param.WriteWithLength(_buf, async, cancellationToken); + param.LengthCache?.Clear(); + _column++; + } + + /// + /// Writes a single null column value. + /// + public void WriteNull() => WriteNull(false).GetAwaiter().GetResult(); + + /// + /// Writes a single null column value. + /// + public Task WriteNullAsync(CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + using (NoSynchronizationContextScope.Enter()) + return WriteNull(true, cancellationToken); + } + + async Task WriteNull(bool async, CancellationToken cancellationToken = default) + { + CheckReady(); + if (_column == -1) + throw new InvalidOperationException("A row hasn't been started"); + + if (_buf.WriteSpaceLeft < 4) + await _buf.Flush(async, cancellationToken); + + _buf.WriteInt32(-1); + _column++; + } + + /// + /// Writes an entire row of columns. + /// Equivalent to calling , followed by multiple + /// on each value. + /// + /// An array of column values to be written as a single row + public void WriteRow(params object[] values) => WriteRow(false, CancellationToken.None, values).GetAwaiter().GetResult(); + + /// + /// Writes an entire row of columns. + /// Equivalent to calling , followed by multiple + /// on each value. + /// + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// An array of column values to be written as a single row + public Task WriteRowAsync(CancellationToken cancellationToken = default, params object[] values) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + using (NoSynchronizationContextScope.Enter()) + return WriteRow(true, cancellationToken, values); + } + + async Task WriteRow(bool async, CancellationToken cancellationToken = default, params object[] values) + { + await StartRow(async, cancellationToken); + foreach (var value in values) + await Write(value, async, cancellationToken); + } + + void CheckColumnIndex() + { + if (_column >= NumColumns) + ThrowHelper.ThrowInvalidOperationException_BinaryImportParametersMismatch(NumColumns, _column + 1); + } + + #endregion + + #region Commit / Cancel / Close / Dispose + + /// + /// Completes the import operation. The writer is unusable after this operation. + /// + public ulong Complete() => Complete(false).GetAwaiter().GetResult(); + + /// + /// Completes the import operation. The writer is unusable after this operation. + /// + public ValueTask CompleteAsync(CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + return new ValueTask(Task.FromCanceled(cancellationToken)); + using (NoSynchronizationContextScope.Enter()) + return Complete(true, cancellationToken); + } + + async ValueTask Complete(bool async, CancellationToken cancellationToken = default) + { + CheckReady(); + + using var registration = _connector.StartNestedCancellableOperation(cancellationToken, attemptPgCancellation: false); + + if (InMiddleOfRow) + { + await Cancel(async, cancellationToken); + throw new InvalidOperationException("Binary importer closed in the middle of a row, cancelling import."); + } + + try + { + await WriteTrailer(async, cancellationToken); + await _buf.Flush(async, cancellationToken); + _buf.EndCopyMode(); + await _connector.WriteCopyDone(async, cancellationToken); + await _connector.Flush(async, cancellationToken); + var cmdComplete = Expect(await _connector.ReadMessage(async), _connector); + Expect(await _connector.ReadMessage(async), _connector); + _state = ImporterState.Committed; + return cmdComplete.Rows; + } + catch + { + Cleanup(); + throw; + } + } + + void ICancelable.Cancel() => Close(); + + async Task ICancelable.CancelAsync() => await CloseAsync(); + + /// + /// + /// Terminates the ongoing binary import and puts the connection back into the idle state, where regular commands can be executed. + /// + /// + /// Note that if hasn't been invoked before calling this, the import will be cancelled and all changes will + /// be reverted. + /// + /// + public void Dispose() => Close(); + + /// + /// + /// Async terminates the ongoing binary import and puts the connection back into the idle state, where regular commands can be executed. + /// + /// + /// Note that if hasn't been invoked before calling this, the import will be cancelled and all changes will + /// be reverted. + /// + /// + public ValueTask DisposeAsync() + { + using (NoSynchronizationContextScope.Enter()) + return CloseAsync(true); + } + + async Task Cancel(bool async, CancellationToken cancellationToken = default) + { + _state = ImporterState.Cancelled; + _buf.Clear(); + _buf.EndCopyMode(); + await _connector.WriteCopyFail(async, cancellationToken); + await _connector.Flush(async, cancellationToken); + try + { + using var registration = _connector.StartNestedCancellableOperation(cancellationToken, attemptPgCancellation: false); + var msg = await _connector.ReadMessage(async); + // The CopyFail should immediately trigger an exception from the read above. + throw _connector.Break( + new NpgsqlException("Expected ErrorResponse when cancelling COPY but got: " + msg.Code)); + } + catch (PostgresException e) + { + if (e.SqlState != PostgresErrorCodes.QueryCanceled) + throw; + } + } + + /// + /// + /// Terminates the ongoing binary import and puts the connection back into the idle state, where regular commands can be executed. + /// + /// + /// Note that if hasn't been invoked before calling this, the import will be cancelled and all changes will + /// be reverted. + /// + /// + public void Close() => CloseAsync(false).GetAwaiter().GetResult(); + + /// + /// + /// Async terminates the ongoing binary import and puts the connection back into the idle state, where regular commands can be executed. + /// + /// + /// Note that if hasn't been invoked before calling this, the import will be cancelled and all changes will + /// be reverted. + /// + /// + public ValueTask CloseAsync(CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + return new ValueTask(Task.FromCanceled(cancellationToken)); + using (NoSynchronizationContextScope.Enter()) + return CloseAsync(true, cancellationToken); + } + + async ValueTask CloseAsync(bool async, CancellationToken cancellationToken = default) + { + switch (_state) + { + case ImporterState.Disposed: + return; + case ImporterState.Ready: + await Cancel(async, cancellationToken); + break; + case ImporterState.Cancelled: + case ImporterState.Committed: + break; + default: + throw new Exception("Invalid state: " + _state); + } + + _connector.EndUserAction(); + Cleanup(); + } + +#pragma warning disable CS8625 + void Cleanup() + { + if (_state == ImporterState.Disposed) + return; + var connector = _connector; + Log.Debug("COPY operation ended", connector?.Id ?? -1); + + if (connector != null) + { + connector.CurrentCopyOperation = null; + _connector.Connection?.EndBindingScope(ConnectorBindingScope.Copy); + _connector = null; + } + + _buf = null; + _state = ImporterState.Disposed; + } +#pragma warning restore CS8625 + + async Task WriteTrailer(bool async, CancellationToken cancellationToken = default) + { + if (_buf.WriteSpaceLeft < 2) + await _buf.Flush(async, cancellationToken); + _buf.WriteInt16(-1); + } + + void CheckReady() + { + switch (_state) + { + case ImporterState.Ready: + return; + case ImporterState.Disposed: + throw new ObjectDisposedException(GetType().FullName, "The COPY operation has already ended."); + case ImporterState.Cancelled: + throw new InvalidOperationException("The COPY operation has already been cancelled."); + case ImporterState.Committed: + throw new InvalidOperationException("The COPY operation has already been committed."); + default: + throw new Exception("Invalid state: " + _state); + } + } + + #endregion + + #region Enums + + enum ImporterState + { + Ready, + Committed, + Cancelled, + Disposed + } + + #endregion Enums +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlCommand.cs b/LibExternal/Npgsql/NpgsqlCommand.cs new file mode 100644 index 0000000..39c4a81 --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlCommand.cs @@ -0,0 +1,1770 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Data.Common; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Globalization; +using Npgsql.BackendMessages; +using Npgsql.Logging; +using Npgsql.Util; +using NpgsqlTypes; +using static Npgsql.Util.Statics; +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using Npgsql.Internal; + +namespace Npgsql; + +/// +/// Represents a SQL statement or function (stored procedure) to execute +/// against a PostgreSQL database. This class cannot be inherited. +/// +// ReSharper disable once RedundantNameQualifier +[System.ComponentModel.DesignerCategory("")] +public sealed class NpgsqlCommand : DbCommand, ICloneable, IComponent +{ + #region Fields + + NpgsqlConnection? _connection; + + readonly NpgsqlConnector? _connector; + + /// + /// If this command is (explicitly) prepared, references the connector on which the preparation happened. + /// Used to detect when the connector was changed (i.e. connection open/close), meaning that the command + /// is no longer prepared. + /// + NpgsqlConnector? _connectorPreparedOn; + + string _commandText; + CommandBehavior _behavior; + int? _timeout; + readonly NpgsqlParameterCollection _parameters; + + /// + /// Whether this is wrapped by an . + /// + internal bool IsWrappedByBatch { get; } + + internal List InternalBatchCommands { get; } + + Activity? CurrentActivity; + + /// + /// Returns details about each statement that this command has executed. + /// Is only populated when an Execute* method is called. + /// + [Obsolete("Use the new DbBatch API")] + public IReadOnlyList Statements => InternalBatchCommands.AsReadOnly(); + + UpdateRowSource _updateRowSource = UpdateRowSource.Both; + + bool IsExplicitlyPrepared => _connectorPreparedOn != null; + + /// + /// Whether this command is cached by and returned by . + /// + internal bool IsCached { get; set; } + +#if DEBUG + internal static bool EnableSqlRewriting; +#else + internal static readonly bool EnableSqlRewriting; +#endif + + static readonly List EmptyParameters = new(); + + static readonly SingleThreadSynchronizationContext SingleThreadSynchronizationContext = new("NpgsqlRemainingAsyncSendWorker"); + + static readonly NpgsqlLogger Log = NpgsqlLogManager.CreateLogger(nameof(NpgsqlCommand)); + + #endregion Fields + + #region Constants + + internal const int DefaultTimeout = 30; + + #endregion + + #region Constructors + + static NpgsqlCommand() + => EnableSqlRewriting = !AppContext.TryGetSwitch("Npgsql.EnableSqlRewriting", out var enabled) || enabled; + + /// + /// Initializes a new instance of the class. + /// + public NpgsqlCommand() : this(null, null, null) {} + + /// + /// Initializes a new instance of the class with the text of the query. + /// + /// The text of the query. + // ReSharper disable once IntroduceOptionalParameters.Global + public NpgsqlCommand(string? cmdText) : this(cmdText, null, null) {} + + /// + /// Initializes a new instance of the class with the text of the query and a + /// . + /// + /// The text of the query. + /// A that represents the connection to a PostgreSQL server. + // ReSharper disable once IntroduceOptionalParameters.Global + public NpgsqlCommand(string? cmdText, NpgsqlConnection? connection) : this(cmdText, connection, null) {} + + /// + /// Initializes a new instance of the class with the text of the query, a + /// , and the . + /// + /// The text of the query. + /// A that represents the connection to a PostgreSQL server. + /// The in which the executes. + public NpgsqlCommand(string? cmdText, NpgsqlConnection? connection, NpgsqlTransaction? transaction) + { + GC.SuppressFinalize(this); + InternalBatchCommands = new List(1); + _parameters = new NpgsqlParameterCollection(); + _commandText = cmdText ?? string.Empty; + _connection = connection; + Transaction = transaction; + CommandType = CommandType.Text; + } + + /// + /// Used when this instance is wrapped inside an . + /// + internal NpgsqlCommand(List batchCommands) + { + GC.SuppressFinalize(this); + InternalBatchCommands = batchCommands; + CommandType = CommandType.Text; + IsWrappedByBatch = true; + + // These can/should never be used in this mode + _commandText = null!; + _parameters = null!; + } + + internal NpgsqlCommand(string? cmdText, NpgsqlConnector connector) : this(cmdText) + => _connector = connector; + + /// + /// Used when this instance is wrapped inside an . + /// + internal NpgsqlCommand(NpgsqlConnector connector, List batchCommands) + : this(batchCommands) + => _connector = connector; + + internal static NpgsqlCommand CreateCachedCommand(NpgsqlConnection connection) + => new(null, connection) { IsCached = true }; + + #endregion Constructors + + #region Public properties + + /// + /// Gets or sets the SQL statement or function (stored procedure) to execute at the data source. + /// + /// The Transact-SQL statement or stored procedure to execute. The default is an empty string. + [AllowNull, DefaultValue("")] + [Category("Data")] + public override string CommandText + { + get => _commandText; + set + { + Debug.Assert(!IsWrappedByBatch); + + _commandText = State == CommandState.Idle + ? value ?? string.Empty + : throw new InvalidOperationException("An open data reader exists for this command."); + + ResetExplicitPreparation(); + // TODO: Technically should do this also if the parameter list (or type) changes + } + } + + /// + /// Gets or sets the wait time (in seconds) before terminating the attempt to execute a command and generating an error. + /// + /// The time (in seconds) to wait for the command to execute. The default value is 30 seconds. + [DefaultValue(DefaultTimeout)] + public override int CommandTimeout + { + get => _timeout ?? (_connection?.CommandTimeout ?? DefaultTimeout); + set + { + if (value < 0) { + throw new ArgumentOutOfRangeException(nameof(value), value, "CommandTimeout can't be less than zero."); + } + + _timeout = value; + } + } + + /// + /// Gets or sets a value indicating how the property is to be interpreted. + /// + /// + /// One of the values. The default is . + /// + [DefaultValue(CommandType.Text)] + [Category("Data")] + public override CommandType CommandType { get; set; } + + /// + /// DB connection. + /// + protected override DbConnection? DbConnection + { + get => _connection; + set => _connection = (NpgsqlConnection?)value; + } + + /// + /// Gets or sets the used by this instance of the . + /// + /// The connection to a data source. The default value is . + [DefaultValue(null)] + [Category("Behavior")] + public new NpgsqlConnection? Connection + { + get => _connection; + set + { + if (_connection == value) + return; + + _connection = State == CommandState.Idle + ? value + : throw new InvalidOperationException("An open data reader exists for this command."); + + Transaction = null; + } + } + + /// + /// Design time visible. + /// + public override bool DesignTimeVisible { get; set; } + + /// + /// Gets or sets how command results are applied to the DataRow when used by the + /// DbDataAdapter.Update(DataSet) method. + /// + /// One of the values. + [Category("Behavior"), DefaultValue(UpdateRowSource.Both)] + public override UpdateRowSource UpdatedRowSource + { + get => _updateRowSource; + set + { + switch (value) + { + // validate value (required based on base type contract) + case UpdateRowSource.None: + case UpdateRowSource.OutputParameters: + case UpdateRowSource.FirstReturnedRecord: + case UpdateRowSource.Both: + _updateRowSource = value; + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + } + + /// + /// Returns whether this query will execute as a prepared (compiled) query. + /// + public bool IsPrepared => + _connectorPreparedOn == (_connection?.Connector ?? _connector) && + InternalBatchCommands.Any() && InternalBatchCommands.All(s => s.PreparedStatement?.IsPrepared == true); + + #endregion Public properties + + #region Known/unknown Result Types Management + + /// + /// Marks all of the query's result columns as either known or unknown. + /// Unknown results column are requested them from PostgreSQL in text format, and Npgsql makes no + /// attempt to parse them. They will be accessible as strings only. + /// + public bool AllResultTypesAreUnknown + { + get => _allResultTypesAreUnknown; + set + { + // TODO: Check that this isn't modified after calling prepare + _unknownResultTypeList = null; + _allResultTypesAreUnknown = value; + } + } + + bool _allResultTypesAreUnknown; + + /// + /// Marks the query's result columns as known or unknown, on a column-by-column basis. + /// Unknown results column are requested them from PostgreSQL in text format, and Npgsql makes no + /// attempt to parse them. They will be accessible as strings only. + /// + /// + /// If the query includes several queries (e.g. SELECT 1; SELECT 2), this will only apply to the first + /// one. The rest of the queries will be fetched and parsed as usual. + /// + /// The array size must correspond exactly to the number of result columns the query returns, or an + /// error will be raised. + /// + public bool[]? UnknownResultTypeList + { + get => _unknownResultTypeList; + set + { + // TODO: Check that this isn't modified after calling prepare + _allResultTypesAreUnknown = false; + _unknownResultTypeList = value; + } + } + + bool[]? _unknownResultTypeList; + + #endregion + + #region Result Types Management + + /// + /// Marks result types to be used when using GetValue on a data reader, on a column-by-column basis. + /// Used for Entity Framework 5-6 compability. + /// Only primitive numerical types and DateTimeOffset are supported. + /// Set the whole array or just a value to null to use default type. + /// + internal Type[]? ObjectResultTypes { get; set; } + + #endregion + + #region State management + + volatile int _state; + + /// + /// The current state of the command + /// + internal CommandState State + { + get => (CommandState)_state; + set + { + var newState = (int)value; + if (newState == _state) + return; + _state = newState; + } + } + + void ResetExplicitPreparation() => _connectorPreparedOn = null; + + #endregion State management + + #region Parameters + + /// + /// Creates a new instance of an object. + /// + /// A object. + protected override DbParameter CreateDbParameter() => CreateParameter(); + + /// + /// Creates a new instance of a object. + /// + /// An object. + public new NpgsqlParameter CreateParameter() => new(); + + /// + /// DB parameter collection. + /// + protected override DbParameterCollection DbParameterCollection => Parameters; + + /// + /// Gets the . + /// + /// The parameters of the SQL statement or function (stored procedure). The default is an empty collection. + public new NpgsqlParameterCollection Parameters => _parameters; + + #endregion + + #region DeriveParameters + + const string DeriveParametersForFunctionQuery = @" +SELECT +CASE + WHEN pg_proc.proargnames IS NULL THEN array_cat(array_fill(''::name,ARRAY[pg_proc.pronargs]),array_agg(pg_attribute.attname ORDER BY pg_attribute.attnum)) + ELSE pg_proc.proargnames +END AS proargnames, +pg_proc.proargtypes, +CASE + WHEN pg_proc.proallargtypes IS NULL AND (array_agg(pg_attribute.atttypid))[1] IS NOT NULL THEN array_cat(string_to_array(pg_proc.proargtypes::text,' ')::oid[],array_agg(pg_attribute.atttypid ORDER BY pg_attribute.attnum)) + ELSE pg_proc.proallargtypes +END AS proallargtypes, +CASE + WHEN pg_proc.proargmodes IS NULL AND (array_agg(pg_attribute.atttypid))[1] IS NOT NULL THEN array_cat(array_fill('i'::""char"",ARRAY[pg_proc.pronargs]),array_fill('o'::""char"",ARRAY[array_length(array_agg(pg_attribute.atttypid), 1)])) + ELSE pg_proc.proargmodes +END AS proargmodes +FROM pg_proc +LEFT JOIN pg_type ON pg_proc.prorettype = pg_type.oid +LEFT JOIN pg_attribute ON pg_type.typrelid = pg_attribute.attrelid AND pg_attribute.attnum >= 1 AND NOT pg_attribute.attisdropped +WHERE pg_proc.oid = :proname::regproc +GROUP BY pg_proc.proargnames, pg_proc.proargtypes, pg_proc.proallargtypes, pg_proc.proargmodes, pg_proc.pronargs; +"; + + internal void DeriveParameters() + { + var conn = CheckAndGetConnection(); + Debug.Assert(conn is not null); + + if (string.IsNullOrEmpty(CommandText)) + throw new InvalidOperationException("CommandText property has not been initialized"); + + using var _ = conn.StartTemporaryBindingScope(out var connector); + + if (InternalBatchCommands.Any(s => s.PreparedStatement?.IsExplicit == true)) + throw new NpgsqlException("Deriving parameters isn't supported for commands that are already prepared."); + + // Here we unprepare statements that possibly are auto-prepared + Unprepare(); + + Parameters.Clear(); + + switch (CommandType) + { + case CommandType.Text: + DeriveParametersForQuery(connector); + break; + case CommandType.StoredProcedure: + DeriveParametersForFunction(); + break; + default: + throw new NotSupportedException("Cannot derive parameters for CommandType " + CommandType); + } + } + + void DeriveParametersForFunction() + { + using var c = new NpgsqlCommand(DeriveParametersForFunctionQuery, _connection); + c.Parameters.Add(new NpgsqlParameter("proname", NpgsqlDbType.Text)); + c.Parameters[0].Value = CommandText; + + string[]? names = null; + uint[]? types = null; + char[]? modes = null; + + using (var rdr = c.ExecuteReader(CommandBehavior.SingleRow | CommandBehavior.SingleResult)) + { + if (rdr.Read()) + { + if (!rdr.IsDBNull(0)) + names = rdr.GetFieldValue(0); + if (!rdr.IsDBNull(2)) + types = rdr.GetFieldValue(2); + if (!rdr.IsDBNull(3)) + modes = rdr.GetFieldValue(3); + if (types == null) + { + if (rdr.IsDBNull(1) || rdr.GetFieldValue(1).Length == 0) + return; // Parameter-less function + types = rdr.GetFieldValue(1); + } + } + else + throw new InvalidOperationException($"{CommandText} does not exist in pg_proc"); + } + + var typeMapper = c._connection!.Connector!.TypeMapper; + + for (var i = 0; i < types.Length; i++) + { + var param = new NpgsqlParameter(); + + var (npgsqlDbType, postgresType) = typeMapper.GetTypeInfoByOid(types[i]); + + param.DataTypeName = postgresType.DisplayName; + param.PostgresType = postgresType; + if (npgsqlDbType.HasValue) + param.NpgsqlDbType = npgsqlDbType.Value; + + if (names != null && i < names.Length) + param.ParameterName = names[i]; + else + param.ParameterName = "parameter" + (i + 1); + + if (modes == null) // All params are IN, or server < 8.1.0 (and only IN is supported) + param.Direction = ParameterDirection.Input; + else + { + param.Direction = modes[i] switch + { + 'i' => ParameterDirection.Input, + 'o' => ParameterDirection.Output, + 't' => ParameterDirection.Output, + 'b' => ParameterDirection.InputOutput, + 'v' => throw new NotSupportedException("Cannot derive function parameter of type VARIADIC"), + _ => throw new ArgumentOutOfRangeException("Unknown code in proargmodes while deriving: " + modes[i]) + }; + } + + Parameters.Add(param); + } + } + + void DeriveParametersForQuery(NpgsqlConnector connector) + { + using (connector.StartUserAction()) + { + Log.Debug($"Deriving Parameters for query: {CommandText}", connector.Id); + + if (IsWrappedByBatch) + foreach (var batchCommand in InternalBatchCommands) + connector.SqlQueryParser.ParseRawQuery(batchCommand, connector.UseConformingStrings, deriveParameters: true); + else + connector.SqlQueryParser.ParseRawQuery(this, connector.UseConformingStrings, deriveParameters: true); + + var sendTask = SendDeriveParameters(connector, false); + if (sendTask.IsFaulted) + sendTask.GetAwaiter().GetResult(); + + foreach (var batchCommand in InternalBatchCommands) + { + Expect( + connector.ReadMessage(async: false).GetAwaiter().GetResult(), connector); + var paramTypeOIDs = Expect( + connector.ReadMessage(async: false).GetAwaiter().GetResult(), connector).TypeOIDs; + + if (batchCommand.PositionalParameters.Count != paramTypeOIDs.Count) + { + connector.SkipUntil(BackendMessageCode.ReadyForQuery); + Parameters.Clear(); + throw new NpgsqlException("There was a mismatch in the number of derived parameters between the Npgsql SQL parser and the PostgreSQL parser. Please report this as bug to the Npgsql developers (https://github.com/npgsql/npgsql/issues)."); + } + + for (var i = 0; i < paramTypeOIDs.Count; i++) + { + try + { + var param = batchCommand.PositionalParameters[i]; + var paramOid = paramTypeOIDs[i]; + + var (npgsqlDbType, postgresType) = connector.TypeMapper.GetTypeInfoByOid(paramOid); + + if (param.NpgsqlDbType != NpgsqlDbType.Unknown && param.NpgsqlDbType != npgsqlDbType) + throw new NpgsqlException("The backend parser inferred different types for parameters with the same name. Please try explicit casting within your SQL statement or batch or use different placeholder names."); + + param.DataTypeName = postgresType.DisplayName; + param.PostgresType = postgresType; + if (npgsqlDbType.HasValue) + param.NpgsqlDbType = npgsqlDbType.Value; + } + catch + { + connector.SkipUntil(BackendMessageCode.ReadyForQuery); + Parameters.Clear(); + throw; + } + } + + var msg = connector.ReadMessage(async: false).GetAwaiter().GetResult(); + switch (msg.Code) + { + case BackendMessageCode.RowDescription: + case BackendMessageCode.NoData: + break; + default: + throw connector.UnexpectedMessageReceived(msg.Code); + } + } + + Expect(connector.ReadMessage(async: false).GetAwaiter().GetResult(), connector); + sendTask.GetAwaiter().GetResult(); + } + } + + #endregion + + #region Prepare + + /// + /// Creates a server-side prepared statement on the PostgreSQL server. + /// This will make repeated future executions of this command much faster. + /// + public override void Prepare() => Prepare(false).GetAwaiter().GetResult(); + + /// + /// Creates a server-side prepared statement on the PostgreSQL server. + /// This will make repeated future executions of this command much faster. + /// + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// +#if NETSTANDARD2_0 + public Task PrepareAsync(CancellationToken cancellationToken = default) +#else + public override Task PrepareAsync(CancellationToken cancellationToken = default) +#endif + { + using (NoSynchronizationContextScope.Enter()) + return Prepare(true, cancellationToken); + } + + Task Prepare(bool async, CancellationToken cancellationToken = default) + { + var connection = CheckAndGetConnection(); + Debug.Assert(connection is not null); + if (connection.Settings.Multiplexing) + throw new NotSupportedException("Explicit preparation not supported with multiplexing"); + var connector = connection.Connector!; + + var needToPrepare = false; + + if (IsWrappedByBatch) + { + foreach (var batchCommand in InternalBatchCommands) + { + batchCommand.Parameters.HasOutputParameters = false; + batchCommand.Parameters.PlaceholderType = PlaceholderType.NoParameters; + + foreach (var p in batchCommand.Parameters.InternalList) + { + batchCommand.Parameters.CalculatePlaceholderType(p); + p.Bind(connector.TypeMapper); + } + + ProcessRawQuery(connector.SqlQueryParser, connector.UseConformingStrings, batchCommand); + + needToPrepare = batchCommand.ExplicitPrepare(connector) || needToPrepare; + } + + if (Log.IsEnabled(NpgsqlLogLevel.Debug)) + Log.Debug(string.Join("; ", InternalBatchCommands.Select(c => c.CommandText)), connector.Id); + } + else + { + Parameters.HasOutputParameters = false; + Parameters.PlaceholderType = PlaceholderType.NoParameters; + + foreach (var p in Parameters.InternalList) + { + Parameters.CalculatePlaceholderType(p); + p.Bind(connector.TypeMapper); + } + + ProcessRawQuery(connector.SqlQueryParser, connector.UseConformingStrings, batchCommand: null); + + foreach (var batchCommand in InternalBatchCommands) + needToPrepare = batchCommand.ExplicitPrepare(connector) || needToPrepare; + + if (Log.IsEnabled(NpgsqlLogLevel.Debug)) + Log.Debug($"Preparing: {CommandText}", connector.Id); + } + + _connectorPreparedOn = connector; + + // It's possible the command was already prepared, or that persistent prepared statements were found for + // all statements. Nothing to do here, move along. + return needToPrepare + ? PrepareLong(this, async, connector, cancellationToken) + : Task.CompletedTask; + + static async Task PrepareLong(NpgsqlCommand command, bool async, NpgsqlConnector connector, CancellationToken cancellationToken) + { + try + { + using (connector.StartUserAction(cancellationToken)) + { + var sendTask = command.SendPrepare(connector, async, CancellationToken.None); + if (sendTask.IsFaulted) + sendTask.GetAwaiter().GetResult(); + + // Loop over statements, skipping those that are already prepared (because they were persisted) + var isFirst = true; + foreach (var batchCommand in command.InternalBatchCommands) + { + if (!batchCommand.IsPreparing) + continue; + + var pStatement = batchCommand.PreparedStatement!; + + if (pStatement.StatementBeingReplaced != null) + { + Expect(await connector.ReadMessage(async), connector); + pStatement.StatementBeingReplaced.CompleteUnprepare(); + pStatement.StatementBeingReplaced = null; + } + + Expect(await connector.ReadMessage(async), connector); + Expect(await connector.ReadMessage(async), connector); + var msg = await connector.ReadMessage(async); + switch (msg.Code) + { + case BackendMessageCode.RowDescription: + // Clone the RowDescription for use with the prepared statement (the one we have is reused + // by the connection) + var description = ((RowDescriptionMessage)msg).Clone(); + command.FixupRowDescription(description, isFirst); + batchCommand.Description = description; + break; + case BackendMessageCode.NoData: + batchCommand.Description = null; + break; + default: + throw connector.UnexpectedMessageReceived(msg.Code); + } + + pStatement.State = PreparedState.Prepared; + connector.PreparedStatementManager.NumPrepared++; + batchCommand.IsPreparing = false; + isFirst = false; + } + + Expect(await connector.ReadMessage(async), connector); + + if (async) + await sendTask; + else + sendTask.GetAwaiter().GetResult(); + } + } + catch + { + // The statements weren't prepared successfully, update the bookkeeping for them + foreach (var batchCommand in command.InternalBatchCommands) + { + if (batchCommand.IsPreparing) + { + batchCommand.IsPreparing = false; + batchCommand.PreparedStatement!.AbortPrepare(); + } + } + + throw; + } + } + } + + /// + /// Unprepares a command, closing server-side statements associated with it. + /// Note that this only affects commands explicitly prepared with , not + /// automatically prepared statements. + /// + public void Unprepare() + => Unprepare(false).GetAwaiter().GetResult(); + + /// + /// Unprepares a command, closing server-side statements associated with it. + /// Note that this only affects commands explicitly prepared with , not + /// automatically prepared statements. + /// + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + public Task UnprepareAsync(CancellationToken cancellationToken = default) + { + using (NoSynchronizationContextScope.Enter()) + return Unprepare(true, cancellationToken); + } + + async Task Unprepare(bool async, CancellationToken cancellationToken = default) + { + var connection = CheckAndGetConnection(); + Debug.Assert(connection is not null); + if (connection.Settings.Multiplexing) + throw new NotSupportedException("Explicit preparation not supported with multiplexing"); + if (InternalBatchCommands.All(s => !s.IsPrepared)) + return; + + var connector = connection.Connector!; + + Log.Debug("Closing command's prepared statements", connector.Id); + + using (connector.StartUserAction(cancellationToken)) + { + var sendTask = SendClose(connector, async, cancellationToken); + if (sendTask.IsFaulted) + sendTask.GetAwaiter().GetResult(); + + foreach (var batchCommand in InternalBatchCommands) + { + if (batchCommand.PreparedStatement?.State == PreparedState.BeingUnprepared) + { + Expect(await connector.ReadMessage(async), connector); + + var pStatement = batchCommand.PreparedStatement; + pStatement.CompleteUnprepare(); + + if (!pStatement.IsExplicit) + connector.PreparedStatementManager.AutoPrepared[pStatement.AutoPreparedSlotIndex] = null; + + batchCommand.PreparedStatement = null; + } + } + + Expect(await connector.ReadMessage(async), connector); + + if (async) + await sendTask; + else + sendTask.GetAwaiter().GetResult(); + } + } + + #endregion Prepare + + #region Query analysis + + internal void ProcessRawQuery(SqlQueryParser? parser, bool standardConformingStrings, NpgsqlBatchCommand? batchCommand) + { + var (commandText, commandType, parameters) = batchCommand is null + ? (CommandText, CommandType, Parameters) + : (batchCommand.CommandText, batchCommand.CommandType, batchCommand.Parameters); + + if (string.IsNullOrEmpty(commandText)) + throw new InvalidOperationException("CommandText property has not been initialized"); + + switch (commandType) + { + case CommandType.Text: + switch (parameters.PlaceholderType) + { + case PlaceholderType.Positional: + // In positional parameter mode, we don't need to parse/rewrite the CommandText or reorder the parameters - just use + // them as is. If the SQL contains a semicolon (legacy batching) when positional parameters are in use, we just send + // that and PostgreSQL will error (this behavior is by-design - use the new batching API). + if (batchCommand is null) + { + batchCommand = TruncateStatementsToOne(); + batchCommand.FinalCommandText = CommandText; + batchCommand.PositionalParameters = Parameters.InternalList; + } + else + { + batchCommand.FinalCommandText = batchCommand.CommandText; + batchCommand.PositionalParameters = batchCommand.Parameters.InternalList; + } + + ValidateParameterCount(batchCommand); + break; + + case PlaceholderType.NoParameters: + // Unless the EnableSqlRewriting AppContext switch is explicitly disabled, queries with no parameters are parsed just + // like queries with named parameters, since they may contain a semicolon (legacy batching). + if (EnableSqlRewriting) + goto case PlaceholderType.Named; + else + goto case PlaceholderType.Positional; + + case PlaceholderType.Named: + if (!EnableSqlRewriting) + throw new NotSupportedException($"Named parameters are not supported when Npgsql.{nameof(EnableSqlRewriting)} is disabled"); + + // The parser is cached on NpgsqlConnector - unless we're in multiplexing mode. + parser ??= new SqlQueryParser(); + + if (batchCommand is null) + { + parser.ParseRawQuery(this, standardConformingStrings); + if (InternalBatchCommands.Count > 1 && _parameters.HasOutputParameters) + throw new NotSupportedException("Commands with multiple queries cannot have out parameters"); + for (var i = 0; i < InternalBatchCommands.Count; i++) + ValidateParameterCount(InternalBatchCommands[i]); + } + else + { + parser.ParseRawQuery(batchCommand, standardConformingStrings); + if (batchCommand.Parameters.HasOutputParameters) + throw new NotSupportedException("Batches cannot cannot have out parameters"); + ValidateParameterCount(batchCommand); + } + + break; + + case PlaceholderType.Mixed: + throw new NotSupportedException("Mixing named and positional parameters isn't supported"); + + default: + throw new ArgumentOutOfRangeException( + nameof(PlaceholderType), $"Unknown {nameof(PlaceholderType)} value: {Parameters.PlaceholderType}"); + } + + break; + + case CommandType.TableDirect: + batchCommand ??= TruncateStatementsToOne(); + batchCommand.FinalCommandText = "SELECT * FROM " + CommandText; + break; + + case CommandType.StoredProcedure: + var inputList = parameters.Where(p => p.IsInputDirection).ToList(); + var numInput = inputList.Count; + var sb = new StringBuilder(); + sb.Append("SELECT * FROM "); + sb.Append(CommandText); + sb.Append('('); + var hasWrittenFirst = false; + for (var i = 1; i <= numInput; i++) { + var param = inputList[i - 1]; + if (param.IsPositional) + { + if (hasWrittenFirst) + sb.Append(','); + sb.Append('$'); + sb.Append(i); + hasWrittenFirst = true; + } + } + for (var i = 1; i <= numInput; i++) + { + var param = inputList[i - 1]; + if (!param.IsPositional) + { + if (hasWrittenFirst) + sb.Append(','); + sb.Append('"'); + sb.Append(param.TrimmedName.Replace("\"", "\"\"")); + sb.Append("\" := "); + sb.Append('$'); + sb.Append(i); + hasWrittenFirst = true; + } + } + sb.Append(')'); + + batchCommand ??= TruncateStatementsToOne(); + batchCommand.FinalCommandText = sb.ToString(); + batchCommand.PositionalParameters.AddRange(inputList); + ValidateParameterCount(batchCommand); + break; + + default: + throw new InvalidOperationException($"Internal Npgsql bug: unexpected value {CommandType} of enum {nameof(CommandType)}. Please file a bug."); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void ValidateParameterCount(NpgsqlBatchCommand batchCommand) + { + if (batchCommand.PositionalParameters.Count > ushort.MaxValue) + throw new NpgsqlException($"A statement cannot have more than {ushort.MaxValue} parameters"); + } + } + + #endregion + + #region Message Creation / Population + + void BeginSend(NpgsqlConnector connector) + => connector.WriteBuffer.Timeout = TimeSpan.FromSeconds(CommandTimeout); + + internal Task Write(NpgsqlConnector connector, bool async, bool flush, CancellationToken cancellationToken = default) + { + return (_behavior & CommandBehavior.SchemaOnly) == 0 + ? WriteExecute(connector, async, flush, cancellationToken) + : WriteExecuteSchemaOnly(connector, async, flush, cancellationToken); + + async Task WriteExecute(NpgsqlConnector connector, bool async, bool flush, CancellationToken cancellationToken) + { + for (var i = 0; i < InternalBatchCommands.Count; i++) + { + // The following is only for deadlock avoidance when doing sync I/O (so never in multiplexing) + ForceAsyncIfNecessary(ref async, i); + + var batchCommand = InternalBatchCommands[i]; + var pStatement = batchCommand.PreparedStatement; + + Debug.Assert(batchCommand.FinalCommandText is not null); + + if (pStatement == null || batchCommand.IsPreparing) + { + // The statement should either execute unprepared, or is being auto-prepared. + // Send Parse, Bind, Describe + + // We may have a prepared statement that replaces an existing statement - close the latter first. + if (pStatement?.StatementBeingReplaced != null) + await connector.WriteClose(StatementOrPortal.Statement, pStatement.StatementBeingReplaced.Name!, async, cancellationToken); + + await connector.WriteParse(batchCommand.FinalCommandText, batchCommand.StatementName, batchCommand.PositionalParameters, async, cancellationToken); + + await connector.WriteBind( + batchCommand.PositionalParameters, string.Empty, batchCommand.StatementName, AllResultTypesAreUnknown, + i == 0 ? UnknownResultTypeList : null, + async, cancellationToken); + + await connector.WriteDescribe(StatementOrPortal.Portal, string.Empty, async, cancellationToken); + } + else + { + // The statement is already prepared, only a Bind is needed + await connector.WriteBind( + batchCommand.PositionalParameters, string.Empty, batchCommand.StatementName, AllResultTypesAreUnknown, + i == 0 ? UnknownResultTypeList : null, + async, cancellationToken); + } + + await connector.WriteExecute(0, async, cancellationToken); + + if (pStatement != null) + pStatement.LastUsed = DateTime.UtcNow; + } + + await connector.WriteSync(async, cancellationToken); + + if (flush) + await connector.Flush(async, cancellationToken); + } + + async Task WriteExecuteSchemaOnly(NpgsqlConnector connector, bool async, bool flush, CancellationToken cancellationToken) + { + var wroteSomething = false; + for (var i = 0; i < InternalBatchCommands.Count; i++) + { + ForceAsyncIfNecessary(ref async, i); + + var batchCommand = InternalBatchCommands[i]; + + if (batchCommand.PreparedStatement?.State == PreparedState.Prepared) + continue; // Prepared, we already have the RowDescription + + await connector.WriteParse(batchCommand.FinalCommandText!, batchCommand.StatementName, batchCommand.PositionalParameters, async, cancellationToken); + await connector.WriteDescribe(StatementOrPortal.Statement, batchCommand.StatementName, async, cancellationToken); + wroteSomething = true; + } + + if (wroteSomething) + { + await connector.WriteSync(async, cancellationToken); + if (flush) + await connector.Flush(async, cancellationToken); + } + } + } + + async Task SendDeriveParameters(NpgsqlConnector connector, bool async, CancellationToken cancellationToken = default) + { + BeginSend(connector); + + for (var i = 0; i < InternalBatchCommands.Count; i++) + { + ForceAsyncIfNecessary(ref async, i); + + var batchCommand = InternalBatchCommands[i]; + + await connector.WriteParse(batchCommand.FinalCommandText!, string.Empty, EmptyParameters, async, cancellationToken); + await connector.WriteDescribe(StatementOrPortal.Statement, string.Empty, async, cancellationToken); + } + + await connector.WriteSync(async, cancellationToken); + await connector.Flush(async, cancellationToken); + } + + async Task SendPrepare(NpgsqlConnector connector, bool async, CancellationToken cancellationToken = default) + { + BeginSend(connector); + + for (var i = 0; i < InternalBatchCommands.Count; i++) + { + ForceAsyncIfNecessary(ref async, i); + + var batchCommand = InternalBatchCommands[i]; + var pStatement = batchCommand.PreparedStatement; + + // A statement may be already prepared, already in preparation (i.e. same statement twice + // in the same command), or we can't prepare (overloaded SQL) + if (!batchCommand.IsPreparing) + continue; + + // We may have a prepared statement that replaces an existing statement - close the latter first. + var statementToClose = pStatement!.StatementBeingReplaced; + if (statementToClose != null) + await connector.WriteClose(StatementOrPortal.Statement, statementToClose.Name!, async, cancellationToken); + + await connector.WriteParse(batchCommand.FinalCommandText!, pStatement.Name!, batchCommand.PositionalParameters, async, cancellationToken); + await connector.WriteDescribe(StatementOrPortal.Statement, pStatement.Name!, async, cancellationToken); + } + + await connector.WriteSync(async, cancellationToken); + await connector.Flush(async, cancellationToken); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void ForceAsyncIfNecessary(ref bool async, int numberOfStatementInBatch) + { + if (!async && numberOfStatementInBatch > 0) + { + // We're synchronously sending the non-first statement in a batch - switch to async writing. + // See long comment in Execute() above. + + // TODO: we can simply do all batch writing asynchronously, instead of starting with the 2nd statement. + // For now, writing the first statement synchronously gives us a better chance of handle and bubbling up errors correctly + // (see sendTask.IsFaulted in Execute()). Once #1323 is done, that shouldn't be needed any more and entire batches should + // be written asynchronously. + async = true; + SynchronizationContext.SetSynchronizationContext(SingleThreadSynchronizationContext); + } + } + + async Task SendClose(NpgsqlConnector connector, bool async, CancellationToken cancellationToken = default) + { + BeginSend(connector); + + var i = 0; + foreach (var batchCommand in InternalBatchCommands.Where(s => s.IsPrepared)) + { + ForceAsyncIfNecessary(ref async, i); + + await connector.WriteClose(StatementOrPortal.Statement, batchCommand.StatementName, async, cancellationToken); + batchCommand.PreparedStatement!.State = PreparedState.BeingUnprepared; + i++; + } + + await connector.WriteSync(async, cancellationToken); + await connector.Flush(async, cancellationToken); + } + + #endregion + + #region Execute Non Query + + /// + /// Executes a SQL statement against the connection and returns the number of rows affected. + /// + /// The number of rows affected if known; -1 otherwise. + public override int ExecuteNonQuery() => ExecuteNonQuery(false, CancellationToken.None).GetAwaiter().GetResult(); + + /// + /// Asynchronous version of + /// + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// A task representing the asynchronous operation, with the number of rows affected if known; -1 otherwise. + public override Task ExecuteNonQueryAsync(CancellationToken cancellationToken) + { + using (NoSynchronizationContextScope.Enter()) + return ExecuteNonQuery(true, cancellationToken); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + async Task ExecuteNonQuery(bool async, CancellationToken cancellationToken) + { + var reader = await ExecuteReader(CommandBehavior.Default, async, cancellationToken); + try + { + while (async ? await reader.NextResultAsync(cancellationToken) : reader.NextResult()) ; + + return reader.RecordsAffected; + } + finally + { + if (async) + await reader.DisposeAsync(); + else + reader.Dispose(); + } + } + + #endregion Execute Non Query + + #region Execute Scalar + + /// + /// Executes the query, and returns the first column of the first row + /// in the result set returned by the query. Extra columns or rows are ignored. + /// + /// The first column of the first row in the result set, + /// or a null reference if the result set is empty. + public override object? ExecuteScalar() => ExecuteScalar(false, CancellationToken.None).GetAwaiter().GetResult(); + + /// + /// Asynchronous version of + /// + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// A task representing the asynchronous operation, with the first column of the + /// first row in the result set, or a null reference if the result set is empty. + public override Task ExecuteScalarAsync(CancellationToken cancellationToken) + { + using (NoSynchronizationContextScope.Enter()) + return ExecuteScalar(true, cancellationToken).AsTask(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + async ValueTask ExecuteScalar(bool async, CancellationToken cancellationToken) + { + var behavior = CommandBehavior.SingleRow; + if (IsWrappedByBatch || !Parameters.HasOutputParameters) + behavior |= CommandBehavior.SequentialAccess; + + var reader = await ExecuteReader(behavior, async, cancellationToken); + try + { + var read = async ? await reader.ReadAsync(cancellationToken) : reader.Read(); + var value = read && reader.FieldCount != 0 ? reader.GetValue(0) : null; + // We read the whole result set to trigger any errors + while (async ? await reader.NextResultAsync(cancellationToken) : reader.NextResult()) ; + return value; + } + finally + { + if (async) + await reader.DisposeAsync(); + else + reader.Dispose(); + } + } + + #endregion Execute Scalar + + #region Execute Reader + + /// + /// Executes the command text against the connection. + /// + /// A task representing the operation. + protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) + => ExecuteReader(behavior); + + /// + /// Executes the command text against the connection. + /// + /// An instance of . + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// A task representing the asynchronous operation. + protected override async Task ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken) + => await ExecuteReaderAsync(behavior, cancellationToken); + + /// + /// Executes the against the + /// and returns a . + /// + /// One of the enumeration values that specified the command behavior. + /// A task representing the operation. + public new NpgsqlDataReader ExecuteReader(CommandBehavior behavior = CommandBehavior.Default) + => ExecuteReader(behavior, async: false, CancellationToken.None).GetAwaiter().GetResult(); + + /// + /// An asynchronous version of , which executes + /// the against the + /// and returns a . + /// + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// A task representing the asynchronous operation. + public new Task ExecuteReaderAsync(CancellationToken cancellationToken = default) + => ExecuteReaderAsync(CommandBehavior.Default, cancellationToken); + + /// + /// An asynchronous version of , + /// which executes the against the + /// and returns a . + /// + /// One of the enumeration values that specified the command behavior. + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// A task representing the asynchronous operation. + public new Task ExecuteReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken = default) + { + using (NoSynchronizationContextScope.Enter()) + return ExecuteReader(behavior, async: true, cancellationToken).AsTask(); + } + + // TODO: Maybe pool these? + internal ManualResetValueTaskSource ExecutionCompletion { get; } + = new(); + + internal async ValueTask ExecuteReader(CommandBehavior behavior, bool async, CancellationToken cancellationToken) + { + var conn = CheckAndGetConnection(); + _behavior = behavior; + + NpgsqlConnector? connector; + if (_connector is not null) + { + Debug.Assert(conn is null); + if (behavior.HasFlag(CommandBehavior.CloseConnection)) + throw new ArgumentException($"{nameof(CommandBehavior.CloseConnection)} is not supported with ${nameof(NpgsqlConnector)}", nameof(behavior)); + connector = _connector; + } + else + { + Debug.Assert(conn is not null); + conn.TryGetBoundConnector(out connector); + } + + try + { + if (connector is not null) + { + cancellationToken.ThrowIfCancellationRequested(); + // We cannot pass a token here, as we'll cancel a non-send query + // Also, we don't pass the cancellation token to StartUserAction, since that would make it scope to the entire action (command execution) + // whereas it should only be scoped to the Execute method. + connector.StartUserAction(ConnectorState.Executing, this, CancellationToken.None); + + Task? sendTask; + + try + { + switch (IsExplicitlyPrepared) + { + case true: + Debug.Assert(_connectorPreparedOn != null); + if (IsWrappedByBatch) + { + foreach (var batchCommand in InternalBatchCommands) + { + if (batchCommand.ConnectorPreparedOn != connector) + { + foreach (var s in InternalBatchCommands) + s.ResetPreparation(); + ResetExplicitPreparation(); + goto case false; + } + + batchCommand.Parameters.ValidateAndBind(connector.TypeMapper); + } + } + else + { + if (_connectorPreparedOn != connector) + { + // The command was prepared, but since then the connector has changed. Detach all prepared statements. + foreach (var s in InternalBatchCommands) + s.PreparedStatement = null; + ResetExplicitPreparation(); + goto case false; + } + Parameters.ValidateAndBind(connector.TypeMapper); + } + + NpgsqlEventSource.Log.CommandStartPrepared(); + break; + + case false: + var numPrepared = 0; + + if (IsWrappedByBatch) + { + for (var i = 0; i < InternalBatchCommands.Count; i++) + { + var batchCommand = InternalBatchCommands[i]; + + batchCommand.Parameters.ValidateAndBind(connector.TypeMapper); + ProcessRawQuery(connector.SqlQueryParser, connector.UseConformingStrings, batchCommand); + + if (connector.Settings.MaxAutoPrepare > 0 && batchCommand.TryAutoPrepare(connector)) + numPrepared++; + } + } + else + { + Parameters.ValidateAndBind(connector.TypeMapper); + ProcessRawQuery(connector.SqlQueryParser, connector.UseConformingStrings, batchCommand: null); + + if (connector.Settings.MaxAutoPrepare > 0) + for (var i = 0; i < InternalBatchCommands.Count; i++) + if (InternalBatchCommands[i].TryAutoPrepare(connector)) + numPrepared++; + } + + if (numPrepared > 0) + { + _connectorPreparedOn = connector; + if (numPrepared == InternalBatchCommands.Count) + NpgsqlEventSource.Log.CommandStartPrepared(); + } + + break; + } + + State = CommandState.InProgress; + + if (Log.IsEnabled(NpgsqlLogLevel.Debug)) + LogCommand(connector); + NpgsqlEventSource.Log.CommandStart(CommandText); + TraceCommandStart(connector); + + // If a cancellation is in progress, wait for it to "complete" before proceeding (#615) + connector.ResetCancellation(); + + // We do not wait for the entire send to complete before proceeding to reading - + // the sending continues in parallel with the user's reading. Waiting for the + // entire send to complete would trigger a deadlock for multi-statement commands, + // where PostgreSQL sends large results for the first statement, while we're sending large + // parameter data for the second. See #641. + // Instead, all sends for non-first statements are performed asynchronously (even if the user requested sync), + // in a special synchronization context to prevents a dependency on the thread pool (which would also trigger + // deadlocks). + BeginSend(connector); + sendTask = Write(connector, async, flush: true, CancellationToken.None); + + // The following is a hack. It raises an exception if one was thrown in the first phases + // of the send (i.e. in parts of the send that executed synchronously). Exceptions may + // still happen later and aren't properly handled. See #1323. + if (sendTask.IsFaulted) + sendTask.GetAwaiter().GetResult(); + } + catch + { + connector.EndUserAction(); + throw; + } + + // TODO: DRY the following with multiplexing, but be careful with the cancellation registration... + var reader = connector.DataReader; + reader.Init(this, behavior, InternalBatchCommands, sendTask); + connector.CurrentReader = reader; + if (async) + await reader.NextResultAsync(cancellationToken); + else + reader.NextResult(); + + TraceReceivedFirstResponse(); + + return reader; + } + else + { + Debug.Assert(conn is not null); + Debug.Assert(conn.Settings.Multiplexing); + // The connection isn't bound to a connector - it's multiplexing time. + var pool = (MultiplexingConnectorPool)conn.Pool; + + if (!async) + { + // The waiting on the ExecutionCompletion ManualResetValueTaskSource is necessarily + // asynchronous, so allowing sync would mean sync-over-async. + throw new NotSupportedException( + "Synchronous command execution is not supported when multiplexing is on"); + } + + if (IsWrappedByBatch) + { + foreach (var batchCommand in InternalBatchCommands) + { + batchCommand.Parameters.ValidateAndBind(pool.MultiplexingTypeMapper!); + ProcessRawQuery(null, standardConformingStrings: true, batchCommand); + } + } + else + { + Parameters.ValidateAndBind(pool.MultiplexingTypeMapper!); + ProcessRawQuery(null, standardConformingStrings: true, batchCommand: null); + } + + State = CommandState.InProgress; + + // TODO: Experiment: do we want to wait on *writing* here, or on *reading*? + // Previous behavior was to wait on reading, which throw the exception from ExecuteReader (and not from + // the first read). But waiting on writing would allow us to do sync writing and async reading. + ExecutionCompletion.Reset(); + await pool.MultiplexCommandWriter!.WriteAsync(this, cancellationToken); + connector = await new ValueTask(ExecutionCompletion, ExecutionCompletion.Version); + // TODO: Overload of StartBindingScope? + conn.Connector = connector; + connector.Connection = conn; + conn.ConnectorBindingScope = ConnectorBindingScope.Reader; + + var reader = connector.DataReader; + reader.Init(this, behavior, InternalBatchCommands); + connector.CurrentReader = reader; + await reader.NextResultAsync(cancellationToken); + + return reader; + } + } + catch (Exception e) + { + var reader = connector?.CurrentReader; + if (!(e is NpgsqlOperationInProgressException) && reader != null) + await reader.Cleanup(async); + + TraceSetException(e); + + State = CommandState.Idle; + + // Reader disposal contains logic for closing the connection if CommandBehavior.CloseConnection is + // specified. However, close here as well in case of an error before the reader was even instantiated + // (e.g. write I/O error) + if ((behavior & CommandBehavior.CloseConnection) == CommandBehavior.CloseConnection) + { + Debug.Assert(_connector is null && conn is not null); + conn.Close(); + } + + throw; + } + } + + #endregion + + #region Transactions + + /// + /// DB transaction. + /// + protected override DbTransaction? DbTransaction + { + get => Transaction; + set => Transaction = (NpgsqlTransaction?)value; + } + /// + /// This property is ignored by Npgsql. PostgreSQL only supports a single transaction at a given time on + /// a given connection, and all commands implicitly run inside the current transaction started via + /// + /// + public new NpgsqlTransaction? Transaction { get; set; } + + #endregion Transactions + + #region Cancel + + /// + /// Attempts to cancel the execution of an . + /// + /// As per the specs, no exception will be thrown by this method in case of failure. + public override void Cancel() + { + if (State != CommandState.InProgress) + return; + + var connector = Connection?.Connector ?? _connector; + if (connector is null) + return; + + connector.PerformUserCancellation(); + } + + #endregion Cancel + + #region Dispose + + /// + /// Releases the resources used by the . + /// + protected override void Dispose(bool disposing) + { + Transaction = null; + + State = CommandState.Disposed; + + if (IsCached && _connection is not null && _connection.CachedCommand is null) + { + // TODO: Optimize NpgsqlParameterCollection to recycle NpgsqlParameter instances as well + // TODO: Statements isn't cleared/recycled, leaving this for now, since it'll be replaced by the new batching API + + _commandText = string.Empty; + CommandType = CommandType.Text; + _parameters.Clear(); + _connection.CachedCommand = this; + return; + } + + IsCached = false; + } + + #endregion + + #region Tracing + + #endregion Tracing + + internal void TraceCommandStart(NpgsqlConnector connector) + { + Debug.Assert(CurrentActivity is null); + if (NpgsqlActivitySource.IsEnabled) + CurrentActivity = NpgsqlActivitySource.CommandStart(connector, CommandText); + } + + internal void TraceReceivedFirstResponse() + { + if (CurrentActivity is not null) + { + NpgsqlActivitySource.ReceivedFirstResponse(CurrentActivity); + } + } + + internal void TraceCommandStop() + { + if (CurrentActivity is not null) + { + NpgsqlActivitySource.CommandStop(CurrentActivity); + CurrentActivity = null; + } + } + + internal void TraceSetException(Exception e) + { + if (CurrentActivity is not null) + { + NpgsqlActivitySource.SetException(CurrentActivity, e); + CurrentActivity = null; + } + } + + #region Misc + + NpgsqlBatchCommand TruncateStatementsToOne() + { + switch (InternalBatchCommands.Count) + { + case 0: + var statement = new NpgsqlBatchCommand(); + InternalBatchCommands.Add(statement); + return statement; + + case 1: + statement = InternalBatchCommands[0]; + statement.Reset(); + return statement; + + default: + statement = InternalBatchCommands[0]; + statement.Reset(); + InternalBatchCommands.Clear(); + InternalBatchCommands.Add(statement); + return statement; + } + } + + /// + /// Fixes up the text/binary flag on result columns. + /// Since Prepare() describes a statement rather than a portal, the resulting RowDescription + /// will have text format on all result columns. Fix that up. + /// + /// + /// Note that UnknownResultTypeList only applies to the first query, while AllResultTypesAreUnknown applies + /// to all of them. + /// + internal void FixupRowDescription(RowDescriptionMessage rowDescription, bool isFirst) + { + for (var i = 0; i < rowDescription.Count; i++) + { + var field = rowDescription[i]; + field.FormatCode = (UnknownResultTypeList == null || !isFirst ? AllResultTypesAreUnknown : UnknownResultTypeList[i]) + ? FormatCode.Text + : FormatCode.Binary; + field.ResolveHandler(); + } + } + + void LogCommand(NpgsqlConnector connector) + { + var sb = new StringBuilder(); + sb.AppendLine("Executing statement(s):"); + foreach (var s in InternalBatchCommands) + { + sb.Append("\t").AppendLine(s.FinalCommandText); + var p = s.PositionalParameters; + if (p.Count > 0 && (NpgsqlLogManager.IsParameterLoggingEnabled || connector.Settings.LogParameters)) + { + for (var i = 0; i < p.Count; i++) + { + sb.Append("\t").Append("Parameter $").Append(i + 1).Append(":"); + switch (p[i].Value) + { + case IList list: + for (var j = 0; j < list.Count; j++) + { + sb.Append("\t#").Append(j).Append(": ").Append(Convert.ToString(list[j], CultureInfo.InvariantCulture)); + } + break; + case DBNull _: + case null: + sb.Append("\t").Append(Convert.ToString("null", CultureInfo.InvariantCulture)); + break; + default: + sb.Append("\t").Append(Convert.ToString(p[i].Value, CultureInfo.InvariantCulture)); + break; + } + sb.AppendLine(); + } + } + } + Log.Debug(sb.ToString(), connector.Id); + connector.QueryLogStopWatch.Start(); + } + + /// + /// Create a new command based on this one. + /// + /// A new NpgsqlCommand object. + object ICloneable.Clone() => Clone(); + + /// + /// Create a new command based on this one. + /// + /// A new NpgsqlCommand object. + public NpgsqlCommand Clone() + { + var clone = new NpgsqlCommand(CommandText, _connection, Transaction) + { + CommandTimeout = CommandTimeout, CommandType = CommandType, DesignTimeVisible = DesignTimeVisible, _allResultTypesAreUnknown = _allResultTypesAreUnknown, _unknownResultTypeList = _unknownResultTypeList, ObjectResultTypes = ObjectResultTypes + }; + _parameters.CloneTo(clone._parameters); + return clone; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + NpgsqlConnection? CheckAndGetConnection() + { + if (State == CommandState.Disposed) + throw new ObjectDisposedException(GetType().FullName); + if (_connection == null) + { + if (_connector is null) + throw new InvalidOperationException("Connection property has not been initialized."); + return null; + } + switch (_connection.FullState) + { + case ConnectionState.Open: + case ConnectionState.Connecting: + case ConnectionState.Open | ConnectionState.Executing: + case ConnectionState.Open | ConnectionState.Fetching: + return _connection; + default: + throw new InvalidOperationException("Connection is not open"); + } + } + + /// + /// This event is unsupported by Npgsql. Use instead. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler? Disposed + { + add => throw new NotSupportedException("The Disposed event isn't supported by Npgsql. Use DbConnection.StateChange instead."); + remove => throw new NotSupportedException("The Disposed event isn't supported by Npgsql. Use DbConnection.StateChange instead."); + } + + event EventHandler? IComponent.Disposed + { + add => Disposed += value; + remove => Disposed -= value; + } + + #endregion +} + +enum CommandState +{ + Idle, + InProgress, + Disposed +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlCommandBuilder.cs b/LibExternal/Npgsql/NpgsqlCommandBuilder.cs new file mode 100644 index 0000000..3508473 --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlCommandBuilder.cs @@ -0,0 +1,302 @@ +using System; +using System.Data; +using System.Data.Common; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using NpgsqlTypes; + +namespace Npgsql; + +/// +/// This class is responsible to create database commands for automatic insert, update and delete operations. +/// +[System.ComponentModel.DesignerCategory("")] +public sealed class NpgsqlCommandBuilder : DbCommandBuilder +{ + // Commented out because SetRowUpdatingHandler() is commented, and causes an "is never used" warning + // private NpgsqlRowUpdatingEventHandler rowUpdatingHandler; + + /// + /// Initializes a new instance of the class. + /// + public NpgsqlCommandBuilder() + : this(null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The adapter. + public NpgsqlCommandBuilder(NpgsqlDataAdapter? adapter) + { + DataAdapter = adapter; + QuotePrefix = "\""; + QuoteSuffix = "\""; + } + + /// + /// Gets or sets the beginning character or characters to use when specifying database objects (for example, tables or columns) whose names contain characters such as spaces or reserved tokens. + /// + /// + /// The beginning character or characters to use. The default is an empty string. + /// + [AllowNull] + public override string QuotePrefix + { + get => base.QuotePrefix; + // TODO: Why should it be possible to remove the QuotePrefix? + set + { + if (string.IsNullOrEmpty(value)) + { + base.QuotePrefix = value; + } + else + { + base.QuotePrefix = "\""; + } + } + } + + /// + /// Gets or sets the ending character or characters to use when specifying database objects (for example, tables or columns) whose names contain characters such as spaces or reserved tokens. + /// + /// + /// The ending character or characters to use. The default is an empty string. + /// + [AllowNull] + public override string QuoteSuffix + { + get => base.QuoteSuffix; + // TODO: Why should it be possible to remove the QuoteSuffix? + set + { + if (string.IsNullOrEmpty(value)) + { + base.QuoteSuffix = value; + } + else + { + base.QuoteSuffix = "\""; + } + } + } + + /// + /// + /// This method is responsible to derive the command parameter list with values obtained from function definition. + /// It clears the Parameters collection of command. Also, if there is any parameter type which is not supported by Npgsql, an InvalidOperationException will be thrown. + /// Parameters name will be parameter1, parameter2, ... for CommandType.StoredProcedure and named after the placeholder for CommandType.Text + /// + /// NpgsqlCommand whose function parameters will be obtained. + public static void DeriveParameters(NpgsqlCommand command) => command.DeriveParameters(); + + /// + /// Gets the automatically generated object required + /// to perform insertions at the data source. + /// + /// + /// The automatically generated object required to perform insertions. + /// + public new NpgsqlCommand GetInsertCommand() => GetInsertCommand(false); + + /// + /// Gets the automatically generated object required to perform insertions + /// at the data source, optionally using columns for parameter names. + /// + /// + /// If , generate parameter names matching column names, if possible. + /// If , generate @p1, @p2, and so on. + /// + /// + /// The automatically generated object required to perform insertions. + /// + public new NpgsqlCommand GetInsertCommand(bool useColumnsForParameterNames) + { + var cmd = (NpgsqlCommand) base.GetInsertCommand(useColumnsForParameterNames); + cmd.UpdatedRowSource = UpdateRowSource.None; + return cmd; + } + + /// + /// Gets the automatically generated System.Data.Common.DbCommand object required + /// to perform updates at the data source. + /// + /// + /// The automatically generated System.Data.Common.DbCommand object required to perform updates. + /// + public new NpgsqlCommand GetUpdateCommand() => GetUpdateCommand(false); + + /// + /// Gets the automatically generated object required to perform updates + /// at the data source, optionally using columns for parameter names. + /// + /// + /// If , generate parameter names matching column names, if possible. + /// If , generate @p1, @p2, and so on. + /// + /// + /// The automatically generated object required to perform updates. + /// + public new NpgsqlCommand GetUpdateCommand(bool useColumnsForParameterNames) + { + var cmd = (NpgsqlCommand)base.GetUpdateCommand(useColumnsForParameterNames); + cmd.UpdatedRowSource = UpdateRowSource.None; + return cmd; + } + + /// + /// Gets the automatically generated System.Data.Common.DbCommand object required + /// to perform deletions at the data source. + /// + /// + /// The automatically generated System.Data.Common.DbCommand object required to perform deletions. + /// + public new NpgsqlCommand GetDeleteCommand() => GetDeleteCommand(false); + + /// + /// Gets the automatically generated object required to perform deletions + /// at the data source, optionally using columns for parameter names. + /// + /// + /// If , generate parameter names matching column names, if possible. + /// If , generate @p1, @p2, and so on. + /// + /// + /// The automatically generated object required to perform deletions. + /// + public new NpgsqlCommand GetDeleteCommand(bool useColumnsForParameterNames) + { + var cmd = (NpgsqlCommand) base.GetDeleteCommand(useColumnsForParameterNames); + cmd.UpdatedRowSource = UpdateRowSource.None; + return cmd; + } + + //never used + //private string QualifiedTableName(string schema, string tableName) + //{ + // if (schema == null || schema.Length == 0) + // { + // return tableName; + // } + // else + // { + // return schema + "." + tableName; + // } + //} + +/* + private static void SetParameterValuesFromRow(NpgsqlCommand command, DataRow row) + { + foreach (NpgsqlParameter parameter in command.Parameters) + { + parameter.Value = row[parameter.SourceColumn, parameter.SourceVersion]; + } + } +*/ + + /// + /// Applies the parameter information. + /// + /// The parameter. + /// The row. + /// Type of the statement. + /// If set to [where clause]. + protected override void ApplyParameterInfo(DbParameter p, DataRow row, System.Data.StatementType statementType, bool whereClause) + { + var param = (NpgsqlParameter)p; + param.NpgsqlDbType = (NpgsqlDbType)row[SchemaTableColumn.ProviderType]; + } + + /// + /// Returns the name of the specified parameter in the format of @p#. + /// + /// The number to be included as part of the parameter's name.. + /// + /// The name of the parameter with the specified number appended as part of the parameter name. + /// + protected override string GetParameterName(int parameterOrdinal) + => string.Format(CultureInfo.InvariantCulture, "@p{0}", parameterOrdinal); + + /// + /// Returns the full parameter name, given the partial parameter name. + /// + /// The partial name of the parameter. + /// + /// The full parameter name corresponding to the partial parameter name requested. + /// + protected override string GetParameterName(string parameterName) + => string.Format(CultureInfo.InvariantCulture, "@{0}", parameterName); + + /// + /// Returns the placeholder for the parameter in the associated SQL statement. + /// + /// The number to be included as part of the parameter's name. + /// + /// The name of the parameter with the specified number appended. + /// + protected override string GetParameterPlaceholder(int parameterOrdinal) + => GetParameterName(parameterOrdinal); + + /// + /// Registers the to handle the event for a . + /// + /// The to be used for the update. + protected override void SetRowUpdatingHandler(DbDataAdapter adapter) + { + var npgsqlAdapter = adapter as NpgsqlDataAdapter; + if (npgsqlAdapter == null) + throw new ArgumentException("adapter needs to be a NpgsqlDataAdapter", nameof(adapter)); + + // Being called twice for the same adapter means unregister + if (adapter == DataAdapter) + npgsqlAdapter.RowUpdating -= RowUpdatingHandler; + else + npgsqlAdapter.RowUpdating += RowUpdatingHandler; + } + + /// + /// Adds an event handler for the event. + /// + /// The sender + /// A instance containing information about the event. + void RowUpdatingHandler(object sender, NpgsqlRowUpdatingEventArgs e) => base.RowUpdatingHandler(e); + + /// + /// Given an unquoted identifier in the correct catalog case, returns the correct quoted form of that identifier, including properly escaping any embedded quotes in the identifier. + /// + /// The original unquoted identifier. + /// + /// The quoted version of the identifier. Embedded quotes within the identifier are properly escaped. + /// + /// Unquoted identifier parameter cannot be null + public override string QuoteIdentifier(string unquotedIdentifier) + => unquotedIdentifier == null + ? throw new ArgumentNullException(nameof(unquotedIdentifier), "Unquoted identifier parameter cannot be null") + : $"{QuotePrefix}{unquotedIdentifier.Replace(QuotePrefix, QuotePrefix + QuotePrefix)}{QuoteSuffix}"; + + /// + /// Given a quoted identifier, returns the correct unquoted form of that identifier, including properly un-escaping any embedded quotes in the identifier. + /// + /// The identifier that will have its embedded quotes removed. + /// + /// The unquoted identifier, with embedded quotes properly un-escaped. + /// + /// Quoted identifier parameter cannot be null + public override string UnquoteIdentifier(string quotedIdentifier) + { + if (quotedIdentifier == null) + throw new ArgumentNullException(nameof(quotedIdentifier), "Quoted identifier parameter cannot be null"); + + var unquotedIdentifier = quotedIdentifier.Trim().Replace(QuotePrefix + QuotePrefix, QuotePrefix); + + if (unquotedIdentifier.StartsWith(QuotePrefix, StringComparison.Ordinal)) + unquotedIdentifier = unquotedIdentifier.Remove(0, 1); + + if (unquotedIdentifier.EndsWith(QuoteSuffix, StringComparison.Ordinal)) + unquotedIdentifier = unquotedIdentifier.Remove(unquotedIdentifier.Length - 1, 1); + + return unquotedIdentifier; + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlConnection.cs b/LibExternal/Npgsql/NpgsqlConnection.cs new file mode 100644 index 0000000..0e5755b --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlConnection.cs @@ -0,0 +1,2185 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Data.Common; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Runtime.Versioning; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; +using System.Transactions; +using Npgsql.Internal; +using Npgsql.Logging; +using Npgsql.NameTranslation; +using Npgsql.Netstandard20; +using Npgsql.TypeMapping; +using Npgsql.Util; +using NpgsqlTypes; +using IsolationLevel = System.Data.IsolationLevel; + +namespace Npgsql; + +/// +/// This class represents a connection to a PostgreSQL server. +/// +// ReSharper disable once RedundantNameQualifier +[System.ComponentModel.DesignerCategory("")] +public sealed class NpgsqlConnection : DbConnection, ICloneable, IComponent +{ + #region Fields + + // Set this when disposed is called. + bool _disposed; + + /// + /// The connection string, without the password after open (unless Persist Security Info=true) + /// + string _userFacingConnectionString = string.Empty; + + /// + /// The original connection string provided by the user, including the password. + /// + string _connectionString = string.Empty; + + internal string OriginalConnectionString => _connectionString; + + ConnectionState _fullState; + + /// + /// The physical connection to the database. This is when the connection is closed, + /// and also when it is open in multiplexing mode and unbound (e.g. not in a transaction). + /// + internal NpgsqlConnector? Connector { get; set; } + + /// + /// The parsed connection string. Set only after the connection is opened. + /// + public NpgsqlConnectionStringBuilder Settings { get; private set; } = DefaultSettings; + + static readonly NpgsqlConnectionStringBuilder DefaultSettings = new(); + + ConnectorSource? _pool; + + internal ConnectorSource Pool + { + get + { + Debug.Assert(_pool is not null); + return _pool; + } + } + + /// + /// A cached command handed out by , which is returned when disposed. Useful for reducing allocations. + /// + internal NpgsqlCommand? CachedCommand { get; set; } + + /// + /// Flag used to make sure we never double-close a connection, returning it twice to the pool. + /// + int _closing; + + internal Transaction? EnlistedTransaction { get; set; } + + /// + /// The global type mapper, which contains defaults used by all new connections. + /// Modify mappings on this mapper to affect your entire application. + /// + public static INpgsqlTypeMapper GlobalTypeMapper => TypeMapping.GlobalTypeMapper.Instance; + + /// + /// The connection-specific type mapper - all modifications affect this connection only, + /// and are lost when it is closed. + /// + public INpgsqlTypeMapper TypeMapper + { + get + { + if (Settings.Multiplexing) + throw new NotSupportedException("Connection-specific type mapping is unsupported when multiplexing is enabled."); + + CheckReady(); + return Connector!.TypeMapper!; + } + } + + /// + /// The default TCP/IP port for PostgreSQL. + /// + public const int DefaultPort = 5432; + + /// + /// Maximum value for connection timeout. + /// + internal const int TimeoutLimit = 1024; + + /// + /// Tracks when this connection was bound to a physical connector (e.g. at open-time, when a transaction + /// was started...). + /// + internal ConnectorBindingScope ConnectorBindingScope { get; set; } + + static readonly NpgsqlLogger Log = NpgsqlLogManager.CreateLogger(nameof(NpgsqlConnection)); + + static readonly StateChangeEventArgs ClosedToOpenEventArgs = new(ConnectionState.Closed, ConnectionState.Open); + static readonly StateChangeEventArgs OpenToClosedEventArgs = new(ConnectionState.Open, ConnectionState.Closed); + + #endregion Fields + + #region Constructors / Init / Open + + /// + /// Initializes a new instance of the class. + /// + public NpgsqlConnection() + => GC.SuppressFinalize(this); + + /// + /// Initializes a new instance of with the given connection string. + /// + /// The connection used to open the PostgreSQL database. + public NpgsqlConnection(string? connectionString) : this() + => ConnectionString = connectionString; + + /// + /// Opens a database connection with the property settings specified by the . + /// + public override void Open() => Open(false, CancellationToken.None).GetAwaiter().GetResult(); + + /// + /// This is the asynchronous version of . + /// + /// + /// Do not invoke other methods and properties of the object until the returned Task is complete. + /// + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// A task representing the asynchronous operation. + public override Task OpenAsync(CancellationToken cancellationToken) + { + using (NoSynchronizationContextScope.Enter()) + return Open(true, cancellationToken); + } + + void GetPoolAndSettings() + { + if (PoolManager.TryGetValue(_connectionString, out _pool)) + { + Settings = _pool.Settings; // Great, we already have a pool + return; + } + + // Connection string hasn't been seen before. Parse it. + var settings = new NpgsqlConnectionStringBuilder(_connectionString); + settings.Validate(); + Settings = settings; + + var hostsSeparator = settings.Host?.IndexOf(','); + if (hostsSeparator is -1) + { + if (settings.TargetSessionAttributesParsed is not null && + settings.TargetSessionAttributesParsed != TargetSessionAttributes.Any) + { + throw new NotSupportedException("Target Session Attributes other then Any is only supported with multiple hosts"); + } + + if (!NpgsqlConnectionStringBuilder.IsUnixSocket(settings.Host!, settings.Port, out _) && + NpgsqlConnectionStringBuilder.TrySplitHostPort(settings.Host!.AsSpan(), out var newHost, out var newPort)) + { + settings.Host = newHost; + settings.Port = newPort; + } + } + + // The connection string may be equivalent to one that has already been seen though (e.g. different + // ordering). Have NpgsqlConnectionStringBuilder produce a canonical string representation + // and recheck. + // Note that we remove TargetSessionAttributes and LoadBalanceHosts to make all connection strings + // that are otherwise identical point to the same pool. + var canonical = settings.ConnectionStringForMultipleHosts; + + if (PoolManager.TryGetValue(canonical, out _pool)) + { + // We're wrapping the original pool in the other, as the original doesn't have the TargetSessionAttributes + if (_pool is MultiHostConnectorPool mhcpCanonical) + _pool = new MultiHostConnectorPoolWrapper(Settings, _connectionString, mhcpCanonical); + // The pool was found, but only under the canonical key - we're using a different version + // for the first time. Map it via our own key for next time. + _pool = PoolManager.GetOrAdd(_connectionString, _pool); + return; + } + + // Really unseen, need to create a new pool + // The canonical pool is the 'base' pool so we need to set that up first. If someone beats us to it use what they put. + // The connection string pool can either be added here or above, if it's added above we should just use that. + ConnectorSource newPool; + if (hostsSeparator.HasValue && hostsSeparator != -1) + { + if (settings.Multiplexing) + throw new NotSupportedException("Multiplexing is not supported with multiple hosts"); + if (settings.ReplicationMode != ReplicationMode.Off) + throw new NotSupportedException("Replication is not supported with multiple hosts"); + newPool = new MultiHostConnectorPool(settings, canonical); + } + else if (settings.Multiplexing) + newPool = new MultiplexingConnectorPool(settings, canonical); + else if (settings.Pooling) + newPool = new ConnectorPool(settings, canonical); + else + newPool = new UnpooledConnectorSource(settings, canonical); + + _pool = PoolManager.GetOrAdd(canonical, newPool); + if (_pool == newPool) + { + Debug.Assert(_pool is not MultiHostConnectorPoolWrapper); + // If the pool we created was the one that ended up being stored we need to increment the appropriate counter. + // Avoids a race condition where multiple threads will create a pool but only one will be stored. + NpgsqlEventSource.Log.PoolCreated(newPool); + } + else + newPool.Dispose(); + + // We're wrapping the original pool in the other, as the original doesn't have the TargetSessionAttributes + if (_pool is MultiHostConnectorPool mhcp) + _pool = new MultiHostConnectorPoolWrapper(Settings, _connectionString, mhcp); + + _pool = PoolManager.GetOrAdd(_connectionString, _pool); + } + + internal Task Open(bool async, CancellationToken cancellationToken) + { + CheckClosed(); + Debug.Assert(Connector == null); + + if (_pool == null) + { + Debug.Assert(string.IsNullOrEmpty(_connectionString)); + + throw new InvalidOperationException("The ConnectionString property has not been initialized."); + } + + FullState = ConnectionState.Connecting; + Log.Trace("Opening connection..."); + + if (Settings.Multiplexing) + { + if (Settings.Enlist && Transaction.Current != null) + { + // TODO: Keep in mind that the TransactionScope can be disposed + throw new NotImplementedException(); + } + + // We're opening in multiplexing mode, without a transaction. We don't actually do anything. + _userFacingConnectionString = Pool.UserFacingConnectionString; + + // If we've never connected with this connection string, open a physical connector in order to generate + // any exception (bad user/password, IP address...). This reproduces the standard error behavior. + if (!((MultiplexingConnectorPool)Pool).IsBootstrapped) + return BootstrapMultiplexing(async, cancellationToken); + + Log.Debug("Connection opened (multipelxing)"); + FullState = ConnectionState.Open; + + return Task.CompletedTask; + } + + return OpenAsync(async, cancellationToken); + + async Task OpenAsync(bool async, CancellationToken cancellationToken) + { + Debug.Assert(!Settings.Multiplexing); + + NpgsqlConnector? connector = null; + try + { + var connectionTimeout = TimeSpan.FromSeconds(ConnectionTimeout); + var timeout = new NpgsqlTimeout(connectionTimeout); + + var enlistToTransaction = Settings.Enlist ? Transaction.Current : null; + + _userFacingConnectionString = _pool.UserFacingConnectionString; + + // First, check to see if we there's an ambient transaction, and we have a connection enlisted + // to this transaction which has been closed. If so, return that as an optimization rather than + // opening a new one and triggering escalation to a distributed transaction. + // Otherwise just get a new connector and enlist below. + if (enlistToTransaction is not null && _pool.TryRentEnlistedPending(enlistToTransaction, this, out connector)) + { + EnlistedTransaction = enlistToTransaction; + enlistToTransaction = null; + } + else + connector = await _pool.Get(this, timeout, async, cancellationToken); + + Debug.Assert(connector.Connection is null, + $"Connection for opened connector '{Connector?.Id.ToString() ?? "???"}' is bound to another connection"); + + // Since this connector was last used, PostgreSQL types (e.g. enums) may have been added + // (and ReloadTypes() called), or global mappings may have changed by the user. + // Bring this up to date if needed. + // Note that in multiplexing execution, the pool-wide type mapper is used so no + // need to update the connector type mapper (this is why this is here). + if (connector.TypeMapper.ChangeCounter != TypeMapping.GlobalTypeMapper.Instance.ChangeCounter) + { + // LoadDatabaseInfo might attempt to execute a query over a connector, which might run in parallel to KeepAlive. + // Start a user action to prevent this. + using var _ = connector.StartUserAction(ConnectorState.Executing, cancellationToken); + await connector.LoadDatabaseInfo(false, timeout, async, cancellationToken); + } + + ConnectorBindingScope = ConnectorBindingScope.Connection; + connector.Connection = this; + Connector = connector; + + if (enlistToTransaction is not null) + EnlistTransaction(enlistToTransaction); + + Log.Debug("Connection opened"); + FullState = ConnectionState.Open; + } + catch + { + FullState = ConnectionState.Closed; + ConnectorBindingScope = ConnectorBindingScope.None; + Connector = null; + EnlistedTransaction = null; + + if (connector is not null) + { + connector.Connection = null; + connector.Return(); + } + + throw; + } + } + + async Task BootstrapMultiplexing(bool async, CancellationToken cancellationToken) + { + try + { + var timeout = new NpgsqlTimeout(TimeSpan.FromSeconds(ConnectionTimeout)); + await ((MultiplexingConnectorPool)Pool).BootstrapMultiplexing(this, timeout, async, cancellationToken); + Log.Debug("Connection opened (multiplexing)"); + FullState = ConnectionState.Open; + } + catch + { + FullState = ConnectionState.Closed; + throw; + } + } + } + + #endregion Open / Init + + #region Connection string management + + /// + /// Gets or sets the string used to connect to a PostgreSQL database. See the manual for details. + /// + /// The connection string that includes the server name, + /// the database name, and other parameters needed to establish + /// the initial connection. The default value is an empty string. + /// + [AllowNull] + public override string ConnectionString + { + get => _userFacingConnectionString; + set + { + CheckClosed(); + + _userFacingConnectionString = _connectionString = value ?? string.Empty; + GetPoolAndSettings(); + } + } + + /// + /// Gets or sets the delegate used to generate a password for new database connections. + /// + /// + ///

+ /// This delegate is executed when a new database connection is opened that requires a password. + ///

+ ///

+ /// The and connection + /// string properties have precedence over this delegate: it will not be executed if a password is specified, or if the specified or + /// default Passfile contains a valid entry. + ///

+ ///

+ /// Due to connection pooling this delegate is only executed when a new physical connection is opened, not when reusing a connection + /// that was previously opened from the pool. + ///

+ ///
+ public ProvidePasswordCallback? ProvidePasswordCallback { get; set; } + + /// + /// Gets or sets the delegate used to setup a connection whenever a physical connection is opened synchronously. + /// + [RequiresPreviewFeatures("Physical open callback is an experimental API, and its exact shape may change in the future")] + public PhysicalOpenCallback? PhysicalOpenCallback { get; set; } + + /// + /// Gets or sets the delegate used to setup a connection whenever a physical connection is opened asynchronously. + /// + [RequiresPreviewFeatures("Physical open callback is an experimental API, and its exact shape may change in the future")] + public PhysicalOpenAsyncCallback? PhysicalOpenAsyncCallback { get; set; } + + #endregion Connection string management + + #region Configuration settings + + /// + /// Backend server host name. + /// + [Browsable(true)] + public string? Host => Connector?.Host; + + /// + /// Backend server port. + /// + [Browsable(true)] + public int Port => Connector?.Port ?? 0; + + /// + /// Gets the time (in seconds) to wait while trying to establish a connection + /// before terminating the attempt and generating an error. + /// + /// The time (in seconds) to wait for a connection to open. The default value is 15 seconds. + public override int ConnectionTimeout => Settings.Timeout; + + /// + /// Gets the time (in seconds) to wait while trying to execute a command + /// before terminating the attempt and generating an error. + /// + /// The time (in seconds) to wait for a command to complete. The default value is 20 seconds. + public int CommandTimeout => Settings.CommandTimeout; + + /// + /// Gets the name of the current database or the database to be used after a connection is opened. + /// + /// The name of the current database or the name of the database to be + /// used after a connection is opened. The default value is the empty string. + public override string Database => Settings.Database ?? Settings.Username ?? ""; + + /// + /// Gets the string identifying the database server (host and port) + /// + /// + /// The name of the database server (host and port). If the connection uses a Unix-domain socket, + /// the path to that socket is returned. The default value is the empty string. + /// + public override string DataSource => Connector?.Settings.DataSourceCached ?? string.Empty; + + /// + /// Whether to use Windows integrated security to log in. + /// + public bool IntegratedSecurity => Settings.IntegratedSecurity; + + /// + /// User name. + /// + public string? UserName => Settings.Username; + + internal string? Password => Settings.Password; + + // The following two lines are here for backwards compatibility with the EF6 provider + // ReSharper disable UnusedMember.Global + internal string? EntityTemplateDatabase => Settings.EntityTemplateDatabase; + internal string? EntityAdminDatabase => Settings.EntityAdminDatabase; + // ReSharper restore UnusedMember.Global + + #endregion Configuration settings + + #region State management + + /// + /// Gets the current state of the connection. + /// + /// A bitwise combination of the values. The default is Closed. + [Browsable(false)] + public ConnectionState FullState + { + // Note: we allow accessing the state after dispose, #164 + get => _fullState switch + { + ConnectionState.Open => Connector == null + ? ConnectionState.Open // When unbound, we only know we're open + : Connector.State switch + { + ConnectorState.Ready => ConnectionState.Open, + ConnectorState.Executing => ConnectionState.Open | ConnectionState.Executing, + ConnectorState.Fetching => ConnectionState.Open | ConnectionState.Fetching, + ConnectorState.Copy => ConnectionState.Open | ConnectionState.Fetching, + ConnectorState.Replication => ConnectionState.Open | ConnectionState.Fetching, + ConnectorState.Waiting => ConnectionState.Open | ConnectionState.Fetching, + ConnectorState.Connecting => ConnectionState.Connecting, + ConnectorState.Broken => ConnectionState.Broken, + ConnectorState.Closed => throw new InvalidOperationException("Internal Npgsql bug: connection is in state Open but connector is in state Closed"), + _ => throw new InvalidOperationException($"Internal Npgsql bug: unexpected value {Connector.State} of enum {nameof(ConnectorState)}. Please file a bug.") + }, + _ => _fullState + }; + internal set + { + var originalOpen = _fullState.HasFlag(ConnectionState.Open); + + _fullState = value; + + var currentOpen = _fullState.HasFlag(ConnectionState.Open); + if (currentOpen != originalOpen) + { + OnStateChange(currentOpen + ? ClosedToOpenEventArgs + : OpenToClosedEventArgs); + } + } + } + + /// + /// Gets whether the current state of the connection is Open or Closed + /// + /// ConnectionState.Open, ConnectionState.Closed or ConnectionState.Connecting + [Browsable(false)] + public override ConnectionState State + { + get + { + var fullState = FullState; + if (fullState.HasFlag(ConnectionState.Connecting)) + return ConnectionState.Connecting; + + if (fullState.HasFlag(ConnectionState.Open)) + return ConnectionState.Open; + + return ConnectionState.Closed; + } + } + + #endregion State management + + #region Command / Batch creation + + /// + /// Creates and returns a + /// object associated with the . + /// + /// A object. + protected override DbCommand CreateDbCommand() => CreateCommand(); + + /// + /// Creates and returns a object associated with the . + /// + /// A object. + public new NpgsqlCommand CreateCommand() + { + CheckDisposed(); + + var cachedCommand = CachedCommand; + if (cachedCommand is not null) + { + CachedCommand = null; + cachedCommand.State = CommandState.Idle; + return cachedCommand; + } + + return NpgsqlCommand.CreateCachedCommand(this); + } + +#if NET6_0_OR_GREATER + /// + public override bool CanCreateBatch => true; + + /// + protected override DbBatch CreateDbBatch() => CreateBatch(); + + /// + public new NpgsqlBatch CreateBatch() => new(this); +#else + /// + /// Creates and returns a object associated with the . + /// + /// A object. + public NpgsqlBatch CreateBatch() => new(this); +#endif + + #endregion Command / Batch creation + + #region Transactions + + /// + /// Begins a database transaction with the specified isolation level. + /// + /// The isolation level under which the transaction should run. + /// A object representing the new transaction. + /// Nested transactions are not supported. + protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) => BeginTransaction(isolationLevel); + + /// + /// Begins a database transaction. + /// + /// A object representing the new transaction. + /// + /// Nested transactions are not supported. + /// Transactions created by this method will have the isolation level. + /// + public new NpgsqlTransaction BeginTransaction() + => BeginTransaction(IsolationLevel.Unspecified); + + /// + /// Begins a database transaction with the specified isolation level. + /// + /// The isolation level under which the transaction should run. + /// A object representing the new transaction. + /// Nested transactions are not supported. + public new NpgsqlTransaction BeginTransaction(IsolationLevel level) + => BeginTransaction(level, async: false, CancellationToken.None).GetAwaiter().GetResult(); + + async ValueTask BeginTransaction(IsolationLevel level, bool async, CancellationToken cancellationToken) + { + if (level == IsolationLevel.Chaos) + throw new NotSupportedException("Unsupported IsolationLevel: " + level); + + CheckReady(); + if (Connector != null && Connector.InTransaction) + throw new InvalidOperationException("A transaction is already in progress; nested/concurrent transactions aren't supported."); + + // There was a commited/rollbacked transaction, but it was not disposed + var connector = ConnectorBindingScope == ConnectorBindingScope.Transaction ? + Connector + : await StartBindingScope(ConnectorBindingScope.Transaction, NpgsqlTimeout.Infinite, async, + cancellationToken); + + Debug.Assert(connector != null); + + try + { + // Note that beginning a transaction doesn't actually send anything to the backend (only prepends). + // But we start a user action to check the cancellation token and generate exceptions + using var _ = connector.StartUserAction(cancellationToken); + + connector.Transaction ??= new NpgsqlTransaction(connector); + connector.Transaction.Init(level); + return connector.Transaction; + } + catch + { + EndBindingScope(ConnectorBindingScope.Transaction); + throw; + } + } + +#if !NETSTANDARD2_0 + /// + /// Asynchronously begins a database transaction. + /// + /// The isolation level under which the transaction should run. + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// A task whose property is an object representing the new transaction. + /// + /// Nested transactions are not supported. + /// + protected override async ValueTask BeginDbTransactionAsync(IsolationLevel isolationLevel, CancellationToken cancellationToken) + => await BeginTransactionAsync(isolationLevel, cancellationToken); + + /// + /// Asynchronously begins a database transaction. + /// + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// A task whose Result property is an object representing the new transaction. + /// + /// Nested transactions are not supported. + /// Transactions created by this method will have the isolation level. + /// + public new ValueTask BeginTransactionAsync(CancellationToken cancellationToken = default) + => BeginTransactionAsync(IsolationLevel.Unspecified, cancellationToken); + + /// + /// Asynchronously begins a database transaction. + /// + /// The isolation level under which the transaction should run. + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// A task whose property is an object representing the new transaction. + /// + /// Nested transactions are not supported. + /// + public new ValueTask BeginTransactionAsync(IsolationLevel level, CancellationToken cancellationToken = default) + { + using (NoSynchronizationContextScope.Enter()) + return BeginTransaction(level, async: true, cancellationToken); + } +#endif + + /// + /// Enlist transaction. + /// + public override void EnlistTransaction(Transaction? transaction) + { + if (Settings.Multiplexing) + throw new NotSupportedException("Ambient transactions aren't yet implemented for multiplexing"); + + if (EnlistedTransaction != null) + { + if (EnlistedTransaction.Equals(transaction)) + return; + try + { + if (EnlistedTransaction.TransactionInformation.Status == System.Transactions.TransactionStatus.Active) + throw new InvalidOperationException($"Already enlisted to transaction (localid={EnlistedTransaction.TransactionInformation.LocalIdentifier})"); + } + catch (ObjectDisposedException) + { + // The MSDTC 2nd phase is asynchronous, so we may end up checking the TransactionInformation on + // a disposed transaction. To be extra safe we catch that, and understand that the transaction + // has ended - no problem for reenlisting. + } + } + + CheckReady(); + var connector = StartBindingScope(ConnectorBindingScope.Transaction); + + EnlistedTransaction = transaction; + if (transaction == null) + { + EnlistedTransaction = null; + return; + } + + // Until #1378 is implemented, we have no recovery, and so no need to enlist as a durable resource manager + // (or as promotable single phase). + + // Note that even when #1378 is implemented in some way, we should check for mono and go volatile in any case - + // distributed transactions aren't supported. + + var volatileResourceManager = new VolatileResourceManager(this, transaction); + transaction.EnlistVolatile(volatileResourceManager, EnlistmentOptions.None); + volatileResourceManager.Init(); + EnlistedTransaction = transaction; + + Log.Debug($"Enlisted volatile resource manager (localid={transaction.TransactionInformation.LocalIdentifier})", connector.Id); + } + + #endregion + + #region Close + + /// + /// Releases the connection. If the connection is pooled, it will be returned to the pool and made available for re-use. + /// If it is non-pooled, the physical connection will be closed. + /// + public override void Close() => Close(async: false).GetAwaiter().GetResult(); + + /// + /// Releases the connection. If the connection is pooled, it will be returned to the pool and made available for re-use. + /// If it is non-pooled, the physical connection will be closed. + /// +#if NETSTANDARD2_0 + public Task CloseAsync() +#else + public override Task CloseAsync() +#endif + { + using (NoSynchronizationContextScope.Enter()) + return Close(async: true); + } + + internal bool TakeCloseLock() => Interlocked.Exchange(ref _closing, 1) == 0; + + internal void ReleaseCloseLock() => Volatile.Write(ref _closing, 0); + + internal Task Close(bool async) + { + // Even though NpgsqlConnection isn't thread safe we'll make sure this part is. + // Because we really don't want double returns to the pool. + if (!TakeCloseLock()) + return Task.CompletedTask; + + switch (FullState) + { + case ConnectionState.Open: + case ConnectionState.Open | ConnectionState.Executing: + case ConnectionState.Open | ConnectionState.Fetching: + break; + case ConnectionState.Broken: + FullState = ConnectionState.Closed; + goto case ConnectionState.Closed; + case ConnectionState.Closed: + ReleaseCloseLock(); + return Task.CompletedTask; + case ConnectionState.Connecting: + ReleaseCloseLock(); + throw new InvalidOperationException("Can't close, connection is in state " + FullState); + default: + ReleaseCloseLock(); + throw new ArgumentOutOfRangeException("Unknown connection state: " + FullState); + } + + // TODO: The following shouldn't exist - we need to flow down the regular path to close any + // open reader / COPY. See test CloseDuringRead with multiplexing. + if (Settings.Multiplexing && ConnectorBindingScope == ConnectorBindingScope.None) + { + // TODO: Consider falling through to the regular reset logic. This adds some unneeded conditions + // and assignment but actual perf impact should be negligible (measure). + Debug.Assert(Connector == null); + ReleaseCloseLock(); + + FullState = ConnectionState.Closed; + Log.Debug("Connection closed (multiplexing)"); + + return Task.CompletedTask; + } + + return CloseAsync(async); + } + + async Task CloseAsync(bool async) + { + Debug.Assert(Connector != null); + Debug.Assert(ConnectorBindingScope != ConnectorBindingScope.None); + + try + { + var connector = Connector; + Log.Trace("Closing connection...", connector.Id); + + if (connector.CurrentReader != null || connector.CurrentCopyOperation != null) + { + // This method could re-enter connection.Close() due to an underlying connection failure. + await connector.CloseOngoingOperations(async); + + if (ConnectorBindingScope == ConnectorBindingScope.None) + { + Debug.Assert(Settings.Multiplexing); + Debug.Assert(Connector is null); + + FullState = ConnectionState.Closed; + Log.Debug("Connection closed (multiplexing, after closing reader)", connector.Id); + return; + } + } + + Debug.Assert(connector.IsReady || connector.IsBroken); + Debug.Assert(connector.CurrentReader == null); + Debug.Assert(connector.CurrentCopyOperation == null); + + if (EnlistedTransaction != null) + { + // A System.Transactions transaction is still in progress + + connector.Connection = null; + + // Close the connection and disconnect it from the resource manager but leave the + // connector in an enlisted pending list in the pool. If another connection is opened within + // the same transaction scope, we will reuse this connector to avoid escalating to a distributed + // transaction + _pool?.AddPendingEnlistedConnector(connector, EnlistedTransaction); + + EnlistedTransaction = null; + } + else + { + if (Settings.Pooling) + { + // Clear the buffer, roll back any pending transaction and prepend a reset message if needed + // Also returns the connector to the pool, if there is an open transaction and multiplexing is on + // Note that we're doing this only for pooled connections + await connector.Reset(async); + } + else + { + // We're already doing the same in the NpgsqlConnector.Reset for pooled connections + // TODO: move reset logic to ConnectorSource.Return + connector.Transaction?.UnbindIfNecessary(); + } + + if (Settings.Multiplexing) + { + // We've already closed ongoing operations rolled back any transaction and the connector is already in the pool, + // so we must be unbound. Nothing to do. + Debug.Assert(ConnectorBindingScope == ConnectorBindingScope.None, + $"When closing a multiplexed connection, the connection was supposed to be unbound, but {nameof(ConnectorBindingScope)} was {ConnectorBindingScope}"); + } + else + { + connector.Connection = null; + connector.Return(); + } + } + + Connector = null; + ConnectorBindingScope = ConnectorBindingScope.None; + FullState = ConnectionState.Closed; + Log.Debug("Connection closed", connector.Id); + } + finally + { + ReleaseCloseLock(); + } + } + + /// + /// Releases all resources used by the . + /// + /// when called from ; + /// when being called from the finalizer. + protected override void Dispose(bool disposing) + { + if (_disposed) + return; + if (disposing) + Close(); + _disposed = true; + } + + /// + /// Releases all resources used by the . + /// +#if NETSTANDARD2_0 + public ValueTask DisposeAsync() +#else + public override ValueTask DisposeAsync() +#endif + { + using (NoSynchronizationContextScope.Enter()) + return DisposeAsyncCore(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + async ValueTask DisposeAsyncCore() + { + if (_disposed) + return; + + await CloseAsync(); + _disposed = true; + } + } + + #endregion + + #region Notifications and Notices + + /// + /// Fires when PostgreSQL notices are received from PostgreSQL. + /// + /// + /// PostgreSQL notices are non-critical messages generated by PostgreSQL, either as a result of a user query + /// (e.g. as a warning or informational notice), or due to outside activity (e.g. if the database administrator + /// initiates a "fast" database shutdown). + /// + /// Note that notices are very different from notifications (see the event). + /// + public event NoticeEventHandler? Notice; + + /// + /// Fires when PostgreSQL notifications are received from PostgreSQL. + /// + /// + /// PostgreSQL notifications are sent when your connection has registered for notifications on a specific channel via the + /// LISTEN command. NOTIFY can be used to generate such notifications, allowing for an inter-connection communication channel. + /// + /// Note that notifications are very different from notices (see the event). + /// + public event NotificationEventHandler? Notification; + + internal void OnNotice(PostgresNotice e) + { + try + { + Notice?.Invoke(this, new NpgsqlNoticeEventArgs(e)); + } + catch (Exception ex) + { + // Block all exceptions bubbling up from the user's event handler + Log.Error("User exception caught when emitting notice event", ex); + } + } + + internal void OnNotification(NpgsqlNotificationEventArgs e) + { + try + { + Notification?.Invoke(this, e); + } + catch (Exception ex) + { + // Block all exceptions bubbling up from the user's event handler + Log.Error("User exception caught when emitting notification event", ex); + } + } + + #endregion Notifications and Notices + + #region SSL + + /// + /// Returns whether SSL is being used for the connection. + /// + internal bool IsSecure => CheckOpenAndRunInTemporaryScope(c => c.IsSecure); + + /// + /// Returns whether SCRAM-SHA256 is being user for the connection + /// + internal bool IsScram => CheckOpenAndRunInTemporaryScope(c => c.IsScram); + + /// + /// Returns whether SCRAM-SHA256-PLUS is being user for the connection + /// + internal bool IsScramPlus => CheckOpenAndRunInTemporaryScope(c => c.IsScramPlus); + + /// + /// Selects the local Secure Sockets Layer (SSL) certificate used for authentication. + /// + /// + /// See + /// + public ProvideClientCertificatesCallback? ProvideClientCertificatesCallback { get; set; } + + /// + /// + /// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication. + /// + /// + /// Cannot be used in conjunction with , and + /// . + /// + /// + /// + /// See + /// + public RemoteCertificateValidationCallback? UserCertificateValidationCallback { get; set; } + + #endregion SSL + + #region Backend version, capabilities, settings + + // TODO: We should probably move DatabaseInfo from each connector to the pool (but remember unpooled) + + /// + /// The version of the PostgreSQL server we're connected to. + /// + ///

+ /// This can only be called when the connection is open. + ///

+ ///

+ /// In case of a development or pre-release version this field will contain + /// the version of the next version to be released from this branch. + ///

+ ///
+ ///
+ [Browsable(false)] + public Version PostgreSqlVersion => CheckOpenAndRunInTemporaryScope(c => c.DatabaseInfo.Version); + + /// + /// The PostgreSQL server version as returned by the server_version option. + /// + /// This can only be called when the connection is open. + /// + /// + public override string ServerVersion => CheckOpenAndRunInTemporaryScope( + c => c.DatabaseInfo.ServerVersion); + + /// + /// Process id of backend server. + /// This can only be called when there is an active connection. + /// + [Browsable(false)] + // ReSharper disable once InconsistentNaming + public int ProcessID + { + get + { + CheckOpen(); + + return TryGetBoundConnector(out var connector) + ? connector.BackendProcessId + : throw new InvalidOperationException("No bound physical connection (using multiplexing)"); + } + } + + /// + /// Reports whether the backend uses the newer integer timestamp representation. + /// Note that the old floating point representation is not supported. + /// Meant for use by type plugins (e.g. NodaTime) + /// + [Browsable(false)] + public bool HasIntegerDateTimes => CheckOpenAndRunInTemporaryScope(c => c.DatabaseInfo.HasIntegerDateTimes); + + /// + /// The connection's timezone as reported by PostgreSQL, in the IANA/Olson database format. + /// + [Browsable(false)] + public string Timezone => CheckOpenAndRunInTemporaryScope(c => c.Timezone); + + /// + /// Holds all PostgreSQL parameters received for this connection. Is updated if the values change + /// (e.g. as a result of a SET command). + /// + [Browsable(false)] + public IReadOnlyDictionary PostgresParameters + => CheckOpenAndRunInTemporaryScope(c => c.PostgresParameters); + + #endregion Backend version, capabilities, settings + + #region Copy + + /// + /// Begins a binary COPY FROM STDIN operation, a high-performance data import mechanism to a PostgreSQL table. + /// + /// A COPY FROM STDIN SQL command + /// A which can be used to write rows and columns + /// + /// See https://www.postgresql.org/docs/current/static/sql-copy.html. + /// + public NpgsqlBinaryImporter BeginBinaryImport(string copyFromCommand) + => BeginBinaryImport(copyFromCommand, async: false, CancellationToken.None).GetAwaiter().GetResult(); + + /// + /// Begins a binary COPY FROM STDIN operation, a high-performance data import mechanism to a PostgreSQL table. + /// + /// A COPY FROM STDIN SQL command + /// An optional token to cancel the asynchronous operation. The default value is None. + /// A which can be used to write rows and columns + /// + /// See https://www.postgresql.org/docs/current/static/sql-copy.html. + /// + public Task BeginBinaryImportAsync(string copyFromCommand, CancellationToken cancellationToken = default) + { + using (NoSynchronizationContextScope.Enter()) + return BeginBinaryImport(copyFromCommand, async: true, cancellationToken); + } + + async Task BeginBinaryImport(string copyFromCommand, bool async, CancellationToken cancellationToken = default) + { + if (copyFromCommand == null) + throw new ArgumentNullException(nameof(copyFromCommand)); + if (!copyFromCommand.TrimStart().ToUpper().StartsWith("COPY", StringComparison.Ordinal)) + throw new ArgumentException("Must contain a COPY FROM STDIN command!", nameof(copyFromCommand)); + + CheckReady(); + var connector = StartBindingScope(ConnectorBindingScope.Copy); + + Log.Debug("Starting binary import", connector.Id); + // no point in passing a cancellationToken here, as we register the cancellation in the Init method + connector.StartUserAction(ConnectorState.Copy, attemptPgCancellation: false); + try + { + var importer = new NpgsqlBinaryImporter(connector); + await importer.Init(copyFromCommand, async, cancellationToken); + connector.CurrentCopyOperation = importer; + return importer; + } + catch + { + connector.EndUserAction(); + EndBindingScope(ConnectorBindingScope.Copy); + throw; + } + } + + /// + /// Begins a binary COPY TO STDOUT operation, a high-performance data export mechanism from a PostgreSQL table. + /// + /// A COPY TO STDOUT SQL command + /// A which can be used to read rows and columns + /// + /// See https://www.postgresql.org/docs/current/static/sql-copy.html. + /// + public NpgsqlBinaryExporter BeginBinaryExport(string copyToCommand) + => BeginBinaryExport(copyToCommand, async: false, CancellationToken.None).GetAwaiter().GetResult(); + + /// + /// Begins a binary COPY TO STDOUT operation, a high-performance data export mechanism from a PostgreSQL table. + /// + /// A COPY TO STDOUT SQL command + /// An optional token to cancel the asynchronous operation. The default value is None. + /// A which can be used to read rows and columns + /// + /// See https://www.postgresql.org/docs/current/static/sql-copy.html. + /// + public Task BeginBinaryExportAsync(string copyToCommand, CancellationToken cancellationToken = default) + { + using (NoSynchronizationContextScope.Enter()) + return BeginBinaryExport(copyToCommand, async: true, cancellationToken); + } + + async Task BeginBinaryExport(string copyToCommand, bool async, CancellationToken cancellationToken = default) + { + if (copyToCommand == null) + throw new ArgumentNullException(nameof(copyToCommand)); + if (!copyToCommand.TrimStart().ToUpper().StartsWith("COPY", StringComparison.Ordinal)) + throw new ArgumentException("Must contain a COPY TO STDOUT command!", nameof(copyToCommand)); + + CheckReady(); + var connector = StartBindingScope(ConnectorBindingScope.Copy); + + Log.Debug("Starting binary export", connector.Id); + // no point in passing a cancellationToken here, as we register the cancellation in the Init method + connector.StartUserAction(ConnectorState.Copy, attemptPgCancellation: false); + try + { + var exporter = new NpgsqlBinaryExporter(connector); + await exporter.Init(copyToCommand, async, cancellationToken); + connector.CurrentCopyOperation = exporter; + return exporter; + } + catch + { + connector.EndUserAction(); + EndBindingScope(ConnectorBindingScope.Copy); + throw; + } + } + + /// + /// Begins a textual COPY FROM STDIN operation, a data import mechanism to a PostgreSQL table. + /// It is the user's responsibility to send the textual input according to the format specified + /// in . + /// + /// A COPY FROM STDIN SQL command + /// + /// A TextWriter that can be used to send textual data. + /// + /// See https://www.postgresql.org/docs/current/static/sql-copy.html. + /// + public TextWriter BeginTextImport(string copyFromCommand) + => BeginTextImport(copyFromCommand, async: false, CancellationToken.None).GetAwaiter().GetResult(); + + /// + /// Begins a textual COPY FROM STDIN operation, a data import mechanism to a PostgreSQL table. + /// It is the user's responsibility to send the textual input according to the format specified + /// in . + /// + /// A COPY FROM STDIN SQL command + /// An optional token to cancel the asynchronous operation. The default value is None. + /// + /// A TextWriter that can be used to send textual data. + /// + /// See https://www.postgresql.org/docs/current/static/sql-copy.html. + /// + public Task BeginTextImportAsync(string copyFromCommand, CancellationToken cancellationToken = default) + { + using (NoSynchronizationContextScope.Enter()) + return BeginTextImport(copyFromCommand, async: true, cancellationToken); + } + + async Task BeginTextImport(string copyFromCommand, bool async, CancellationToken cancellationToken = default) + { + if (copyFromCommand == null) + throw new ArgumentNullException(nameof(copyFromCommand)); + if (!copyFromCommand.TrimStart().ToUpper().StartsWith("COPY", StringComparison.Ordinal)) + throw new ArgumentException("Must contain a COPY FROM STDIN command!", nameof(copyFromCommand)); + + CheckReady(); + var connector = StartBindingScope(ConnectorBindingScope.Copy); + + Log.Debug("Starting text import", connector.Id); + // no point in passing a cancellationToken here, as we register the cancellation in the Init method + connector.StartUserAction(ConnectorState.Copy, attemptPgCancellation: false); + try + { + var copyStream = new NpgsqlRawCopyStream(connector); + await copyStream.Init(copyFromCommand, async, cancellationToken); + var writer = new NpgsqlCopyTextWriter(connector, copyStream); + connector.CurrentCopyOperation = writer; + return writer; + } + catch + { + connector.EndUserAction(); + EndBindingScope(ConnectorBindingScope.Copy); + throw; + } + } + + /// + /// Begins a textual COPY TO STDOUT operation, a data export mechanism from a PostgreSQL table. + /// It is the user's responsibility to parse the textual input according to the format specified + /// in . + /// + /// A COPY TO STDOUT SQL command + /// + /// A TextReader that can be used to read textual data. + /// + /// See https://www.postgresql.org/docs/current/static/sql-copy.html. + /// + public TextReader BeginTextExport(string copyToCommand) + => BeginTextExport(copyToCommand, async: false, CancellationToken.None).GetAwaiter().GetResult(); + + /// + /// Begins a textual COPY TO STDOUT operation, a data export mechanism from a PostgreSQL table. + /// It is the user's responsibility to parse the textual input according to the format specified + /// in . + /// + /// A COPY TO STDOUT SQL command + /// An optional token to cancel the asynchronous operation. The default value is None. + /// + /// A TextReader that can be used to read textual data. + /// + /// See https://www.postgresql.org/docs/current/static/sql-copy.html. + /// + public Task BeginTextExportAsync(string copyToCommand, CancellationToken cancellationToken = default) + { + using (NoSynchronizationContextScope.Enter()) + return BeginTextExport(copyToCommand, async: true, cancellationToken); + } + + async Task BeginTextExport(string copyToCommand, bool async, CancellationToken cancellationToken = default) + { + if (copyToCommand == null) + throw new ArgumentNullException(nameof(copyToCommand)); + if (!copyToCommand.TrimStart().ToUpper().StartsWith("COPY", StringComparison.Ordinal)) + throw new ArgumentException("Must contain a COPY TO STDOUT command!", nameof(copyToCommand)); + + CheckReady(); + var connector = StartBindingScope(ConnectorBindingScope.Copy); + + Log.Debug("Starting text export", connector.Id); + // no point in passing a cancellationToken here, as we register the cancellation in the Init method + connector.StartUserAction(ConnectorState.Copy, attemptPgCancellation: false); + try + { + var copyStream = new NpgsqlRawCopyStream(connector); + await copyStream.Init(copyToCommand, async, cancellationToken); + var reader = new NpgsqlCopyTextReader(connector, copyStream); + connector.CurrentCopyOperation = reader; + return reader; + } + catch + { + connector.EndUserAction(); + EndBindingScope(ConnectorBindingScope.Copy); + throw; + } + } + + /// + /// Begins a raw binary COPY operation (TO STDOUT or FROM STDIN), a high-performance data export/import mechanism to a PostgreSQL table. + /// Note that unlike the other COPY API methods, doesn't implement any encoding/decoding + /// and is unsuitable for structured import/export operation. It is useful mainly for exporting a table as an opaque + /// blob, for the purpose of importing it back later. + /// + /// A COPY TO STDOUT or COPY FROM STDIN SQL command + /// A that can be used to read or write raw binary data. + /// + /// See https://www.postgresql.org/docs/current/static/sql-copy.html. + /// + public NpgsqlRawCopyStream BeginRawBinaryCopy(string copyCommand) + => BeginRawBinaryCopy(copyCommand, async: false, CancellationToken.None).GetAwaiter().GetResult(); + + /// + /// Begins a raw binary COPY operation (TO STDOUT or FROM STDIN), a high-performance data export/import mechanism to a PostgreSQL table. + /// Note that unlike the other COPY API methods, doesn't implement any encoding/decoding + /// and is unsuitable for structured import/export operation. It is useful mainly for exporting a table as an opaque + /// blob, for the purpose of importing it back later. + /// + /// A COPY TO STDOUT or COPY FROM STDIN SQL command + /// An optional token to cancel the asynchronous operation. The default value is None. + /// A that can be used to read or write raw binary data. + /// + /// See https://www.postgresql.org/docs/current/static/sql-copy.html. + /// + public Task BeginRawBinaryCopyAsync(string copyCommand, CancellationToken cancellationToken = default) + { + using (NoSynchronizationContextScope.Enter()) + return BeginRawBinaryCopy(copyCommand, async: true, cancellationToken); + } + + async Task BeginRawBinaryCopy(string copyCommand, bool async, CancellationToken cancellationToken = default) + { + if (copyCommand == null) + throw new ArgumentNullException(nameof(copyCommand)); + if (!copyCommand.TrimStart().ToUpper().StartsWith("COPY", StringComparison.Ordinal)) + throw new ArgumentException("Must contain a COPY TO STDOUT OR COPY FROM STDIN command!", nameof(copyCommand)); + + CheckReady(); + var connector = StartBindingScope(ConnectorBindingScope.Copy); + + Log.Debug("Starting raw COPY operation", connector.Id); + // no point in passing a cancellationToken here, as we register the cancellation in the Init method + connector.StartUserAction(ConnectorState.Copy, attemptPgCancellation: false); + try + { + var stream = new NpgsqlRawCopyStream(connector); + await stream.Init(copyCommand, async, cancellationToken); + if (!stream.IsBinary) + { + // TODO: Stop the COPY operation gracefully, no breaking + throw connector.Break(new ArgumentException( + "copyToCommand triggered a text transfer, only binary is allowed", nameof(copyCommand))); + } + connector.CurrentCopyOperation = stream; + return stream; + } + catch + { + connector.EndUserAction(); + EndBindingScope(ConnectorBindingScope.Copy); + throw; + } + } + + #endregion + + #region Enum mapping + + /// + /// Maps a CLR enum to a PostgreSQL enum type for use with this connection. + /// + /// + /// CLR enum labels are mapped by name to PostgreSQL enum labels. + /// The translation strategy can be controlled by the parameter, + /// which defaults to . + /// You can also use the on your enum fields to manually specify a PostgreSQL enum label. + /// If there is a discrepancy between the .NET and database labels while an enum is read or written, + /// an exception will be raised. + /// + /// Can only be invoked on an open connection; if the connection is closed the mapping is lost. + /// + /// To avoid mapping the type for each connection, use the method. + /// + /// + /// A PostgreSQL type name for the corresponding enum type in the database. + /// If null, the name translator given in will be used. + /// + /// + /// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class). + /// Defaults to + /// + /// The .NET enum type to be mapped + [Obsolete("Use NpgsqlConnection.TypeMapper.MapEnum() instead")] + public void MapEnum(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) + where TEnum : struct, Enum + => TypeMapper.MapEnum(pgName, nameTranslator); + + /// + /// Maps a CLR enum to a PostgreSQL enum type for use with all connections created from now on. Existing connections aren't affected. + /// + /// + /// CLR enum labels are mapped by name to PostgreSQL enum labels. + /// The translation strategy can be controlled by the parameter, + /// which defaults to . + /// You can also use the on your enum fields to manually specify a PostgreSQL enum label. + /// If there is a discrepancy between the .NET and database labels while an enum is read or written, + /// an exception will be raised. + /// + /// To map the type for a specific connection, use the method. + /// + /// + /// A PostgreSQL type name for the corresponding enum type in the database. + /// If null, the name translator given in will be used. + /// + /// + /// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class). + /// Defaults to + /// + /// The .NET enum type to be mapped + [Obsolete("Use NpgsqlConnection.GlobalTypeMapper.MapEnum() instead")] + public static void MapEnumGlobally(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) + where TEnum : struct, Enum + => GlobalTypeMapper.MapEnum(pgName, nameTranslator); + + /// + /// Removes a previous global enum mapping. + /// + /// + /// A PostgreSQL type name for the corresponding enum type in the database. + /// If null, the name translator given in will be used. + /// + /// + /// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class). + /// Defaults to + /// + [Obsolete("Use NpgsqlConnection.GlobalTypeMapper.UnmapEnum() instead")] + public static void UnmapEnumGlobally(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) + where TEnum : struct, Enum + => GlobalTypeMapper.UnmapEnum(pgName, nameTranslator); + + #endregion + + #region Composite registration + + /// + /// Maps a CLR type to a PostgreSQL composite type for use with this connection. + /// + /// + /// CLR fields and properties by string to PostgreSQL enum labels. + /// The translation strategy can be controlled by the parameter, + /// which defaults to . + /// You can also use the on your members to manually specify a PostgreSQL enum label. + /// If there is a discrepancy between the .NET and database labels while a composite is read or written, + /// an exception will be raised. + /// + /// Can only be invoked on an open connection; if the connection is closed the mapping is lost. + /// + /// To avoid mapping the type for each connection, use the method. + /// + /// + /// A PostgreSQL type name for the corresponding enum type in the database. + /// If null, the name translator given in will be used. + /// + /// + /// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class). + /// Defaults to + /// + /// The .NET type to be mapped + [Obsolete("Use NpgsqlConnection.TypeMapper.MapComposite() instead")] + [RequiresUnreferencedCode("Composite type mapping currently isn't trimming-safe.")] + public void MapComposite(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) where T : new() + => TypeMapper.MapComposite(pgName, nameTranslator); + + /// + /// Maps a CLR type to a PostgreSQL composite type for use with all connections created from now on. Existing connections aren't affected. + /// + /// + /// CLR fields and properties by string to PostgreSQL enum labels. + /// The translation strategy can be controlled by the parameter, + /// which defaults to . + /// You can also use the on your members to manually specify a PostgreSQL enum label. + /// If there is a discrepancy between the .NET and database labels while a composite is read or written, + /// an exception will be raised. + /// + /// To map the type for a specific connection, use the method. + /// + /// + /// A PostgreSQL type name for the corresponding enum type in the database. + /// If null, the name translator given in will be used. + /// + /// + /// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class). + /// Defaults to + /// + /// The .NET type to be mapped + [Obsolete("Use NpgsqlConnection.GlobalTypeMapper.MapComposite() instead")] + [RequiresUnreferencedCode("Composite type mapping currently isn't trimming-safe.")] + public static void MapCompositeGlobally(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) where T : new() + => GlobalTypeMapper.MapComposite(pgName, nameTranslator); + + /// + /// Removes a previous global enum mapping. + /// + /// + /// A PostgreSQL type name for the corresponding enum type in the database. + /// If null, the name translator given in will be used. + /// + /// + /// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class). + /// Defaults to + /// + [Obsolete("Use NpgsqlConnection.GlobalTypeMapper.UnmapComposite() instead")] + [RequiresUnreferencedCode("Composite type mapping currently isn't trimming-safe.")] + public static void UnmapCompositeGlobally(string pgName, INpgsqlNameTranslator? nameTranslator = null) where T : new() + => GlobalTypeMapper.UnmapComposite(pgName, nameTranslator); + + #endregion + + #region Wait + + /// + /// Waits until an asynchronous PostgreSQL messages (e.g. a notification) arrives, and + /// exits immediately. The asynchronous message is delivered via the normal events + /// (, ). + /// + /// + /// The time-out value, in milliseconds, passed to . + /// The default value is 0, which indicates an infinite time-out period. + /// Specifying -1 also indicates an infinite time-out period. + /// + /// true if an asynchronous message was received, false if timed out. + public bool Wait(int timeout) + { + if (timeout != -1 && timeout < 0) + throw new ArgumentException("Argument must be -1, 0 or positive", nameof(timeout)); + if (Settings.Multiplexing) + throw new NotSupportedException($"{nameof(Wait)} isn't supported in multiplexing mode"); + + CheckReady(); + + Log.Debug($"Starting to wait (timeout={timeout})...", Connector!.Id); + return Connector!.Wait(async: false, timeout, CancellationToken.None).GetAwaiter().GetResult(); + } + + /// + /// Waits until an asynchronous PostgreSQL messages (e.g. a notification) arrives, and + /// exits immediately. The asynchronous message is delivered via the normal events + /// (, ). + /// + /// + /// The time-out value is passed to . + /// + /// true if an asynchronous message was received, false if timed out. + public bool Wait(TimeSpan timeout) => Wait((int)timeout.TotalMilliseconds); + + /// + /// Waits until an asynchronous PostgreSQL messages (e.g. a notification) arrives, and + /// exits immediately. The asynchronous message is delivered via the normal events + /// (, ). + /// + public void Wait() => Wait(0); + + /// + /// Waits asynchronously until an asynchronous PostgreSQL messages (e.g. a notification) + /// arrives, and exits immediately. The asynchronous message is delivered via the normal events + /// (, ). + /// + /// + /// The time-out value, in milliseconds. + /// The default value is 0, which indicates an infinite time-out period. + /// Specifying -1 also indicates an infinite time-out period. + /// + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// true if an asynchronous message was received, false if timed out. + public Task WaitAsync(int timeout, CancellationToken cancellationToken = default) + { + if (Settings.Multiplexing) + throw new NotSupportedException($"{nameof(Wait)} isn't supported in multiplexing mode"); + + CheckReady(); + + Log.Debug("Starting to wait asynchronously...", Connector!.Id); + using (NoSynchronizationContextScope.Enter()) + return Connector!.Wait(async: true, timeout, cancellationToken); + } + + /// + /// Waits asynchronously until an asynchronous PostgreSQL messages (e.g. a notification) + /// arrives, and exits immediately. The asynchronous message is delivered via the normal events + /// (, ). + /// + /// + /// The time-out value as + /// + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// true if an asynchronous message was received, false if timed out. + public Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToken = default) => WaitAsync((int)timeout.TotalMilliseconds, cancellationToken); + + /// + /// Waits asynchronously until an asynchronous PostgreSQL messages (e.g. a notification) + /// arrives, and exits immediately. The asynchronous message is delivered via the normal events + /// (, ). + /// + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + public Task WaitAsync(CancellationToken cancellationToken = default) => WaitAsync(0, cancellationToken); + + #endregion + + #region State checks + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void CheckOpen() + { + CheckDisposed(); + + switch (FullState) + { + case ConnectionState.Open: + case ConnectionState.Open | ConnectionState.Executing: + case ConnectionState.Open | ConnectionState.Fetching: + case ConnectionState.Connecting: + break; + case ConnectionState.Closed: + case ConnectionState.Broken: + throw new InvalidOperationException("Connection is not open"); + default: + throw new ArgumentOutOfRangeException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void CheckClosed() + { + CheckDisposed(); + + switch (FullState) + { + case ConnectionState.Closed: + case ConnectionState.Broken: + break; + case ConnectionState.Open: + case ConnectionState.Connecting: + case ConnectionState.Open | ConnectionState.Executing: + case ConnectionState.Open | ConnectionState.Fetching: + throw new InvalidOperationException("Connection already open"); + default: + throw new ArgumentOutOfRangeException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void CheckDisposed() + { + if (_disposed) + throw new ObjectDisposedException(typeof(NpgsqlConnection).Name); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void CheckReady() + { + CheckDisposed(); + + switch (FullState) + { + case ConnectionState.Open: + case ConnectionState.Connecting: // We need to do type loading as part of connecting + break; + case ConnectionState.Closed: + case ConnectionState.Broken: + throw new InvalidOperationException("Connection is not open"); + case ConnectionState.Open | ConnectionState.Executing: + case ConnectionState.Open | ConnectionState.Fetching: + throw new InvalidOperationException("Connection is busy"); + default: + throw new ArgumentOutOfRangeException(); + } + } + + #endregion State checks + + #region Connector binding + + /// + /// Checks whether the connection is currently bound to a connector, and if so, returns it via + /// . + /// + internal bool TryGetBoundConnector([NotNullWhen(true)] out NpgsqlConnector? connector) + { + if (ConnectorBindingScope == ConnectorBindingScope.None) + { + Debug.Assert(Connector == null, $"Binding scope is None but {Connector} exists"); + connector = null; + return false; + } + Debug.Assert(Connector != null, $"Binding scope is {ConnectorBindingScope} but {Connector} is null"); + Debug.Assert(Connector.Connection == this, $"Bound connector {Connector} does not reference this connection"); + connector = Connector; + return true; + } + + /// + /// Binds this connection to a physical connector. This happens when opening a non-multiplexing connection, + /// or when starting a transaction on a multiplexed connection. + /// + internal ValueTask StartBindingScope( + ConnectorBindingScope scope, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken) + { + // If the connection is around bound at a higher scope, we do nothing (e.g. copy operation started + // within a transaction on a multiplexing connection). + // Note that if we're in an ambient transaction, that means we're already bound and so we do nothing here. + if (ConnectorBindingScope != ConnectorBindingScope.None) + { + Debug.Assert(Connector != null, $"Connection bound with scope {ConnectorBindingScope} but has no connector"); + Debug.Assert(scope != ConnectorBindingScope, $"Binding scopes aren't reentrant ({ConnectorBindingScope})"); + return new ValueTask(Connector); + } + + return StartBindingScopeAsync(); + + async ValueTask StartBindingScopeAsync() + { + try + { + Debug.Assert(Settings.Multiplexing); + Debug.Assert(_pool != null); + + var connector = await _pool.Get(this, timeout, async, cancellationToken); + Connector = connector; + connector.Connection = this; + ConnectorBindingScope = scope; + return connector; + } + catch + { + FullState = ConnectionState.Broken; + throw; + } + } + } + + internal NpgsqlConnector StartBindingScope(ConnectorBindingScope scope) + => StartBindingScope(scope, NpgsqlTimeout.Infinite, async: false, CancellationToken.None) + .GetAwaiter().GetResult(); + + internal EndScopeDisposable StartTemporaryBindingScope(out NpgsqlConnector connector) + { + connector = StartBindingScope(ConnectorBindingScope.Temporary); + return new EndScopeDisposable(this); + } + + internal T CheckOpenAndRunInTemporaryScope(Func func) + { + CheckOpen(); + + using var _ = StartTemporaryBindingScope(out var connector); + var result = func(connector); + return result; + } + + /// + /// Ends binding scope to the physical connection and returns it to the pool. Only useful with multiplexing on. + /// + /// + /// After this method is called, under no circumstances the physical connection (connector) should ever be used if multiplexing is on. + /// See #3249. + /// + internal void EndBindingScope(ConnectorBindingScope scope) + { + Debug.Assert(ConnectorBindingScope != ConnectorBindingScope.None || FullState == ConnectionState.Broken, + $"Ending binding scope {scope} but connection's scope is null"); + + if (scope != ConnectorBindingScope) + return; + + Debug.Assert(Connector != null, $"Ending binding scope {scope} but connector is null"); + Debug.Assert(_pool != null, $"Ending binding scope {scope} but _pool is null"); + Debug.Assert(Settings.Multiplexing, $"Ending binding scope {scope} but multiplexing is disabled"); + + // TODO: If enlisted transaction scope is still active, need to AddPendingEnlistedConnector, just like Close + var connector = Connector; + Connector = null; + connector.Connection = null; + connector.Transaction?.UnbindIfNecessary(); + connector.Return(); + ConnectorBindingScope = ConnectorBindingScope.None; + } + + #endregion Connector binding + + #region Schema operations + + /// + /// Returns the supported collections + /// + [UnconditionalSuppressMessage( + "Composite type mapping currently isn't trimming-safe, and warnings are generated at the MapComposite level.", "IL2026")] + public override DataTable GetSchema() + => GetSchema("MetaDataCollections", null); + + /// + /// Returns the schema collection specified by the collection name. + /// + /// The collection name. + /// The collection specified. + public override DataTable GetSchema(string? collectionName) => GetSchema(collectionName, null); + + /// + /// Returns the schema collection specified by the collection name filtered by the restrictions. + /// + /// The collection name. + /// + /// The restriction values to filter the results. A description of the restrictions is contained + /// in the Restrictions collection. + /// + /// The collection specified. + public override DataTable GetSchema(string? collectionName, string?[]? restrictions) + => NpgsqlSchema.GetSchema(this, collectionName, restrictions, async: false).GetAwaiter().GetResult(); + + /// + /// Asynchronously returns the supported collections. + /// + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// The collection specified. +#if NET5_0_OR_GREATER + public override Task GetSchemaAsync(CancellationToken cancellationToken = default) +#else + public Task GetSchemaAsync(CancellationToken cancellationToken = default) +#endif + => GetSchemaAsync("MetaDataCollections", null, cancellationToken); + + /// + /// Asynchronously returns the schema collection specified by the collection name. + /// + /// The collection name. + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// The collection specified. +#if NET5_0_OR_GREATER + public override Task GetSchemaAsync(string collectionName, CancellationToken cancellationToken = default) +#else + public Task GetSchemaAsync(string collectionName, CancellationToken cancellationToken = default) +#endif + => GetSchemaAsync(collectionName, null, cancellationToken); + + /// + /// Asynchronously returns the schema collection specified by the collection name filtered by the restrictions. + /// + /// The collection name. + /// + /// The restriction values to filter the results. A description of the restrictions is contained + /// in the Restrictions collection. + /// + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// The collection specified. +#if NET5_0_OR_GREATER + public override Task GetSchemaAsync(string collectionName, string?[]? restrictions, CancellationToken cancellationToken = default) +#else + public Task GetSchemaAsync(string collectionName, string?[]? restrictions, CancellationToken cancellationToken = default) +#endif + { + using (NoSynchronizationContextScope.Enter()) + return NpgsqlSchema.GetSchema(this, collectionName, restrictions, async: true, cancellationToken); + } + + #endregion Schema operations + + #region Misc + + /// + /// Creates a closed connection with the connection string and authentication details of this message. + /// + object ICloneable.Clone() + { + CheckDisposed(); + var conn = new NpgsqlConnection(_connectionString) { + ProvideClientCertificatesCallback = ProvideClientCertificatesCallback, + UserCertificateValidationCallback = UserCertificateValidationCallback, + ProvidePasswordCallback = ProvidePasswordCallback, + _userFacingConnectionString = _userFacingConnectionString + }; + return conn; + } + + /// + /// Clones this connection, replacing its connection string with the given one. + /// This allows creating a new connection with the same security information + /// (password, SSL callbacks) while changing other connection parameters (e.g. + /// database or pooling) + /// + public NpgsqlConnection CloneWith(string connectionString) + { + CheckDisposed(); + var csb = new NpgsqlConnectionStringBuilder(connectionString); + if (csb.Password == null && Password != null) + csb.Password = Password; + if (csb.PersistSecurityInfo && !Settings.PersistSecurityInfo) + csb.PersistSecurityInfo = false; + return new NpgsqlConnection(csb.ToString()) { + ProvideClientCertificatesCallback = ProvideClientCertificatesCallback, + UserCertificateValidationCallback = UserCertificateValidationCallback, + ProvidePasswordCallback = ProvidePasswordCallback, + }; + } + + /// + /// This method changes the current database by disconnecting from the actual + /// database and connecting to the specified. + /// + /// The name of the database to use in place of the current database. + public override void ChangeDatabase(string dbName) + { + if (dbName == null) + throw new ArgumentNullException(nameof(dbName)); + if (string.IsNullOrEmpty(dbName)) + throw new ArgumentOutOfRangeException(nameof(dbName), dbName, $"Invalid database name: {dbName}"); + + CheckOpen(); + Close(); + + _pool = null; + Settings = Settings.Clone(); + Settings.Database = dbName; + ConnectionString = Settings.ToString(); + + Open(); + } + + /// + /// DB provider factory. + /// + protected override DbProviderFactory DbProviderFactory => NpgsqlFactory.Instance; + + /// + /// Clears the connection pool. All idle physical connections in the pool of the given connection are + /// immediately closed, and any busy connections which were opened before was called + /// will be closed when returned to the pool. + /// + public static void ClearPool(NpgsqlConnection connection) => PoolManager.Clear(connection._connectionString); + + /// + /// Clear all connection pools. All idle physical connections in all pools are immediately closed, and any busy + /// connections which were opened before was called will be closed when returned + /// to their pool. + /// + public static void ClearAllPools() => PoolManager.ClearAll(); + + /// + /// Unprepares all prepared statements on this connection. + /// + public void UnprepareAll() + { + if (Settings.Multiplexing) + throw new NotSupportedException("Explicit preparation not supported with multiplexing"); + + CheckReady(); + + using (Connector!.StartUserAction()) + Connector.UnprepareAll(); + } + + /// + /// Flushes the type cache for this connection's connection string and reloads the types for this connection only. + /// Type changes will appear for other connections only after they are re-opened from the pool. + /// + public void ReloadTypes() + { + CheckReady(); + using var scope = StartTemporaryBindingScope(out var connector); + using var _ = connector.StartUserAction(ConnectorState.Executing); + connector.LoadDatabaseInfo( + forceReload: true, + NpgsqlTimeout.Infinite, + async: false, + CancellationToken.None).GetAwaiter().GetResult(); + + // Increment the change counter on the global type mapper. This will make conn.Open() pick up the + // new DatabaseInfo and set up a new connection type mapper + TypeMapping.GlobalTypeMapper.Instance.RecordChange(); + + if (Settings.Multiplexing) + { + var multiplexingTypeMapper = ((MultiplexingConnectorPool)Pool).MultiplexingTypeMapper!; + Debug.Assert(multiplexingTypeMapper == connector.TypeMapper, + "A connector must reference the exact same TypeMapper the MultiplexingConnectorPool does"); + // It's very probable that we've called ReloadTypes on the different connection than + // the MultiplexingConnectorPool references. + // Which means, we have to explicitly call Reset after we change the connector's DatabaseInfo to reload type mappings. + multiplexingTypeMapper.Connector.DatabaseInfo = connector.TypeMapper.DatabaseInfo; + multiplexingTypeMapper.Reset(); + } + } + + /// + /// This event is unsupported by Npgsql. Use instead. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler? Disposed + { + add => throw new NotSupportedException("The Disposed event isn't supported by Npgsql. Use DbConnection.StateChange instead."); + remove => throw new NotSupportedException("The Disposed event isn't supported by Npgsql. Use DbConnection.StateChange instead."); + } + + event EventHandler? IComponent.Disposed + { + add => Disposed += value; + remove => Disposed -= value; + } + + #endregion Misc +} + +enum ConnectorBindingScope +{ + /// + /// The connection is currently not bound to a connector. + /// + None, + + /// + /// The connection is bound to its connector for the scope of the entire connection + /// (i.e. non-multiplexed connection). + /// + Connection, + + /// + /// The connection is bound to its connector for the scope of a transaction. + /// + Transaction, + + /// + /// The connection is bound to its connector for the scope of a COPY operation. + /// + Copy, + + /// + /// The connection is bound to its connector for the scope of a single reader. + /// + Reader, + + /// + /// The connection is bound to its connector for an unspecified, temporary scope; the code that initiated + /// the binding is also responsible to unbind it. + /// + Temporary +} + +readonly struct EndScopeDisposable : IDisposable +{ + readonly NpgsqlConnection _connection; + public EndScopeDisposable(NpgsqlConnection connection) => _connection = connection; + public void Dispose() => _connection.EndBindingScope(ConnectorBindingScope.Temporary); +} + +#region Delegates + +/// +/// Represents a method that handles the event. +/// +/// The source of the event. +/// A that contains the notice information (e.g. message, severity...). +public delegate void NoticeEventHandler(object sender, NpgsqlNoticeEventArgs e); + +/// +/// Represents a method that handles the event. +/// +/// The source of the event. +/// A that contains the notification payload. +public delegate void NotificationEventHandler(object sender, NpgsqlNotificationEventArgs e); + +/// +/// Represents a method that allows the application to provide a certificate collection to be used for SSL client authentication +/// +/// +/// A to be filled with one or more client +/// certificates. +/// +public delegate void ProvideClientCertificatesCallback(X509CertificateCollection certificates); + +/// +/// Represents a method that allows the application to provide a password at connection time in code rather than configuration +/// +/// Hostname +/// Port +/// Database Name +/// User +/// A valid password for connecting to the database +public delegate string ProvidePasswordCallback(string host, int port, string database, string username); + +/// +/// Represents a method that allows the application to setup a connection with custom commands. +/// +/// Physical connection to the database +[RequiresPreviewFeatures("Physical open callback is an experimental API, and its exact shape may change in the future")] +public delegate void PhysicalOpenCallback(NpgsqlConnector connection); + +/// +/// Represents an asynchronous method that allows the application to setup a connection with custom commands. +/// +/// Physical connection to the database +[RequiresPreviewFeatures("Physical open callback is an experimental API, and its exact shape may change in the future")] +public delegate Task PhysicalOpenAsyncCallback(NpgsqlConnector connection); + +#endregion diff --git a/LibExternal/Npgsql/NpgsqlConnectionStringBuilder.cs b/LibExternal/Npgsql/NpgsqlConnectionStringBuilder.cs new file mode 100644 index 0000000..14d6ffa --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlConnectionStringBuilder.cs @@ -0,0 +1,1898 @@ +using System; +using System.IO; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data.Common; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Npgsql.Internal; +using Npgsql.Netstandard20; +using Npgsql.Replication; + +namespace Npgsql; + +/// +/// Provides a simple way to create and manage the contents of connection strings used by +/// the class. +/// +public sealed partial class NpgsqlConnectionStringBuilder : DbConnectionStringBuilder, IDictionary +{ + #region Fields + + /// + /// Cached DataSource value to reduce allocations on NpgsqlConnection.DataSource.get + /// + string? _dataSourceCached; + + internal string DataSourceCached + => _dataSourceCached ??= _host is null + ? string.Empty + : IsUnixSocket(_host, _port, out var socketPath, replaceForAbstract: false) + ? socketPath + : $"tcp://{_host}:{_port}"; + + // Note that we can't cache the result due to nullable's assignment not being thread safe + internal TimeSpan HostRecheckSecondsTranslated + => TimeSpan.FromSeconds(HostRecheckSeconds == 0 ? -1 : HostRecheckSeconds); + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the NpgsqlConnectionStringBuilder class. + /// + public NpgsqlConnectionStringBuilder() => Init(); + + /// + /// Initializes a new instance of the NpgsqlConnectionStringBuilder class, optionally using ODBC rules for quoting values. + /// + /// true to use {} to delimit fields; false to use quotation marks. + public NpgsqlConnectionStringBuilder(bool useOdbcRules) : base(useOdbcRules) => Init(); + + /// + /// Initializes a new instance of the NpgsqlConnectionStringBuilder class and sets its . + /// + public NpgsqlConnectionStringBuilder(string? connectionString) + { + Init(); + ConnectionString = connectionString; + } + + // Method fake-returns an int only to make sure it's code-generated + private partial int Init(); + + #endregion + + #region Non-static property handling + + /// + /// Gets or sets the value associated with the specified key. + /// + /// The key of the item to get or set. + /// The value associated with the specified key. + [AllowNull] + public override object this[string keyword] + { + get + { + if (!TryGetValue(keyword, out var value)) + throw new ArgumentException("Keyword not supported: " + keyword, nameof(keyword)); + return value; + } + set + { + if (value is null) + { + Remove(keyword); + return; + } + + try + { + GeneratedSetter(keyword.ToUpperInvariant(), value); + } + catch (Exception e) + { + throw new ArgumentException("Couldn't set " + keyword, keyword, e); + } + } + } + + // Method fake-returns an int only to make sure it's code-generated + private partial int GeneratedSetter(string keyword, object? value); + + object? IDictionary.this[string keyword] + { + get => this[keyword]; + set => this[keyword] = value!; + } + + /// + /// Adds an item to the . + /// + /// The key-value pair to be added. + public void Add(KeyValuePair item) + => this[item.Key] = item.Value!; + + void IDictionary.Add(string keyword, object? value) + => this[keyword] = value; + + /// + /// Removes the entry with the specified key from the DbConnectionStringBuilder instance. + /// + /// The key of the key/value pair to be removed from the connection string in this DbConnectionStringBuilder. + /// true if the key existed within the connection string and was removed; false if the key did not exist. + public override bool Remove(string keyword) + => RemoveGenerated(keyword.ToUpperInvariant()); + + private partial bool RemoveGenerated(string keyword); + + /// + /// Removes the entry from the DbConnectionStringBuilder instance. + /// + /// The key/value pair to be removed from the connection string in this DbConnectionStringBuilder. + /// true if the key existed within the connection string and was removed; false if the key did not exist. + public bool Remove(KeyValuePair item) + => Remove(item.Key); + + /// + /// Clears the contents of the instance. + /// + public override void Clear() + { + Debug.Assert(Keys != null); + foreach (var k in Keys.ToArray()) + Remove(k); + } + + /// + /// Determines whether the contains a specific key. + /// + /// The key to locate in the . + /// true if the contains an entry with the specified key; otherwise false. + public override bool ContainsKey(string keyword) + => keyword is null + ? throw new ArgumentNullException(nameof(keyword)) + : ContainsKeyGenerated(keyword.ToUpperInvariant()); + + private partial bool ContainsKeyGenerated(string keyword); + + /// + /// Determines whether the contains a specific key-value pair. + /// + /// The item to locate in the . + /// true if the contains the entry; otherwise false. + public bool Contains(KeyValuePair item) + => TryGetValue(item.Key, out var value) && + ((value == null && item.Value == null) || (value != null && value.Equals(item.Value))); + + /// + /// Retrieves a value corresponding to the supplied key from this . + /// + /// The key of the item to retrieve. + /// The value corresponding to the key. + /// true if keyword was found within the connection string, false otherwise. + public override bool TryGetValue(string keyword, [NotNullWhen(true)] out object? value) + { + if (keyword == null) + throw new ArgumentNullException(nameof(keyword)); + + return TryGetValueGenerated(keyword.ToUpperInvariant(), out value); + } + + private partial bool TryGetValueGenerated(string keyword, [NotNullWhen(true)] out object? value); + + void SetValue(string propertyName, object? value) + { + var canonicalKeyword = ToCanonicalKeyword(propertyName.ToUpperInvariant()); + if (value == null) + base.Remove(canonicalKeyword); + else + base[canonicalKeyword] = value; + } + + private partial string ToCanonicalKeyword(string keyword); + + #endregion + + #region Properties - Connection + + /// + /// The hostname or IP address of the PostgreSQL server to connect to. + /// + [Category("Connection")] + [Description("The hostname or IP address of the PostgreSQL server to connect to.")] + [DisplayName("Host")] + [NpgsqlConnectionStringProperty("Server")] + public string? Host + { + get => _host; + set + { + _host = value; + SetValue(nameof(Host), value); + _dataSourceCached = null; + } + } + string? _host; + + /// + /// The TCP/IP port of the PostgreSQL server. + /// + [Category("Connection")] + [Description("The TCP port of the PostgreSQL server.")] + [DisplayName("Port")] + [NpgsqlConnectionStringProperty] + [DefaultValue(NpgsqlConnection.DefaultPort)] + public int Port + { + get => _port; + set + { + if (value <= 0) + throw new ArgumentOutOfRangeException(nameof(value), value, "Invalid port: " + value); + + _port = value; + SetValue(nameof(Port), value); + _dataSourceCached = null; + } + } + int _port; + + /// + /// The PostgreSQL database to connect to. + /// + [Category("Connection")] + [Description("The PostgreSQL database to connect to.")] + [DisplayName("Database")] + [NpgsqlConnectionStringProperty("DB")] + public string? Database + { + get => _database; + set + { + _database = value; + SetValue(nameof(Database), value); + } + } + string? _database; + + /// + /// The username to connect with. Not required if using IntegratedSecurity. + /// + [Category("Connection")] + [Description("The username to connect with. Not required if using IntegratedSecurity.")] + [DisplayName("Username")] + [NpgsqlConnectionStringProperty("User Name", "UserId", "User Id", "UID")] + public string? Username + { + get => _username; + set + { + _username = value; + SetValue(nameof(Username), value); + } + } + string? _username; + + /// + /// The password to connect with. Not required if using IntegratedSecurity. + /// + [Category("Connection")] + [Description("The password to connect with. Not required if using IntegratedSecurity.")] + [PasswordPropertyText(true)] + [DisplayName("Password")] + [NpgsqlConnectionStringProperty("PSW", "PWD")] + public string? Password + { + get => _password; + set + { + _password = value; + SetValue(nameof(Password), value); + } + } + string? _password; + + /// + /// Path to a PostgreSQL password file (PGPASSFILE), from which the password would be taken. + /// + [Category("Connection")] + [Description("Path to a PostgreSQL password file (PGPASSFILE), from which the password would be taken.")] + [DisplayName("Passfile")] + [NpgsqlConnectionStringProperty] + public string? Passfile + { + get => _passfile; + set + { + _passfile = value; + SetValue(nameof(Passfile), value); + } + } + + string? _passfile; + + /// + /// The optional application name parameter to be sent to the backend during connection initiation. + /// + [Category("Connection")] + [Description("The optional application name parameter to be sent to the backend during connection initiation")] + [DisplayName("Application Name")] + [NpgsqlConnectionStringProperty] + public string? ApplicationName + { + get => _applicationName; + set + { + _applicationName = value; + SetValue(nameof(ApplicationName), value); + } + } + string? _applicationName; + + /// + /// Whether to enlist in an ambient TransactionScope. + /// + [Category("Connection")] + [Description("Whether to enlist in an ambient TransactionScope.")] + [DisplayName("Enlist")] + [DefaultValue(true)] + [NpgsqlConnectionStringProperty] + public bool Enlist + { + get => _enlist; + set + { + _enlist = value; + SetValue(nameof(Enlist), value); + } + } + bool _enlist; + + /// + /// Gets or sets the schema search path. + /// + [Category("Connection")] + [Description("Gets or sets the schema search path.")] + [DisplayName("Search Path")] + [NpgsqlConnectionStringProperty] + public string? SearchPath + { + get => _searchPath; + set + { + _searchPath = value; + SetValue(nameof(SearchPath), value); + } + } + string? _searchPath; + + /// + /// Gets or sets the client_encoding parameter. + /// + [Category("Connection")] + [Description("Gets or sets the client_encoding parameter.")] + [DisplayName("Client Encoding")] + [NpgsqlConnectionStringProperty] + public string? ClientEncoding + { + get => _clientEncoding; + set + { + _clientEncoding = value; + SetValue(nameof(ClientEncoding), value); + } + } + string? _clientEncoding; + + /// + /// Gets or sets the .NET encoding that will be used to encode/decode PostgreSQL string data. + /// + [Category("Connection")] + [Description("Gets or sets the .NET encoding that will be used to encode/decode PostgreSQL string data.")] + [DisplayName("Encoding")] + [DefaultValue("UTF8")] + [NpgsqlConnectionStringProperty] + public string Encoding + { + get => _encoding; + set + { + _encoding = value; + SetValue(nameof(Encoding), value); + } + } + string _encoding = "UTF8"; + + /// + /// Gets or sets the PostgreSQL session timezone, in Olson/IANA database format. + /// + [Category("Connection")] + [Description("Gets or sets the PostgreSQL session timezone, in Olson/IANA database format.")] + [DisplayName("Timezone")] + [NpgsqlConnectionStringProperty] + public string? Timezone + { + get => _timezone; + set + { + _timezone = value; + SetValue(nameof(Timezone), value); + } + } + string? _timezone; + + #endregion + + #region Properties - Security + + /// + /// Controls whether SSL is required, disabled or preferred, depending on server support. + /// + [Category("Security")] + [Description("Controls whether SSL is required, disabled or preferred, depending on server support.")] + [DisplayName("SSL Mode")] + [DefaultValue(SslMode.Prefer)] + [NpgsqlConnectionStringProperty] + public SslMode SslMode + { + get => _sslMode; + set + { + _sslMode = value; + SetValue(nameof(SslMode), value); + } + } + SslMode _sslMode; + + /// + /// Whether to trust the server certificate without validating it. + /// + [Category("Security")] + [Description("Whether to trust the server certificate without validating it.")] + [DisplayName("Trust Server Certificate")] + [NpgsqlConnectionStringProperty] + public bool TrustServerCertificate + { + get => _trustServerCertificate; + set + { + _trustServerCertificate = value; + SetValue(nameof(TrustServerCertificate), value); + } + } + bool _trustServerCertificate; + + /// + /// Location of a client certificate to be sent to the server. + /// + [Category("Security")] + [Description("Location of a client certificate to be sent to the server.")] + [DisplayName("SSL Certificate")] + [NpgsqlConnectionStringProperty] + public string? SslCertificate + { + get => _sslCertificate; + set + { + _sslCertificate = value; + SetValue(nameof(SslCertificate), value); + } + } + string? _sslCertificate; + + /// + /// Location of a client key for a client certificate to be sent to the server. + /// + [Category("Security")] + [Description("Location of a client key for a client certificate to be sent to the server.")] + [DisplayName("SSL Key")] + [NpgsqlConnectionStringProperty] + public string? SslKey + { + get => _sslKey; + set + { + _sslKey = value; + SetValue(nameof(SslKey), value); + } + } + string? _sslKey; + + /// + /// Password for a key for a client certificate. + /// + [Category("Security")] + [Description("Password for a key for a client certificate.")] + [DisplayName("SSL Password")] + [NpgsqlConnectionStringProperty] + public string? SslPassword + { + get => _sslPassword; + set + { + _sslPassword = value; + SetValue(nameof(SslPassword), value); + } + } + string? _sslPassword; + + /// + /// Location of a CA certificate used to validate the server certificate. + /// + [Category("Security")] + [Description("Location of a CA certificate used to validate the server certificate.")] + [DisplayName("Root Certificate")] + [NpgsqlConnectionStringProperty] + public string? RootCertificate + { + get => _rootCertificate; + set + { + _rootCertificate = value; + SetValue(nameof(RootCertificate), value); + } + } + string? _rootCertificate; + + /// + /// Whether to check the certificate revocation list during authentication. + /// False by default. + /// + [Category("Security")] + [Description("Whether to check the certificate revocation list during authentication.")] + [DisplayName("Check Certificate Revocation")] + [NpgsqlConnectionStringProperty] + public bool CheckCertificateRevocation + { + get => _checkCertificateRevocation; + set + { + _checkCertificateRevocation = value; + SetValue(nameof(CheckCertificateRevocation), value); + } + } + bool _checkCertificateRevocation; + + /// + /// Whether to use Windows integrated security to log in. + /// + [Category("Security")] + [Description("Whether to use Windows integrated security to log in.")] + [DisplayName("Integrated Security")] + [NpgsqlConnectionStringProperty] + public bool IntegratedSecurity + { + get => _integratedSecurity; + set + { + // No integrated security if we're on mono and .NET 4.5 because of ClaimsIdentity, + // see https://github.com/npgsql/Npgsql/issues/133 + if (value && Type.GetType("Mono.Runtime") != null) + throw new NotSupportedException("IntegratedSecurity is currently unsupported on mono and .NET 4.5 (see https://github.com/npgsql/Npgsql/issues/133)"); + _integratedSecurity = value; + SetValue(nameof(IntegratedSecurity), value); + } + } + bool _integratedSecurity; + + /// + /// The Kerberos service name to be used for authentication. + /// + [Category("Security")] + [Description("The Kerberos service name to be used for authentication.")] + [DisplayName("Kerberos Service Name")] + [NpgsqlConnectionStringProperty("Krbsrvname")] + [DefaultValue("postgres")] + public string KerberosServiceName + { + get => _kerberosServiceName; + set + { + _kerberosServiceName = value; + SetValue(nameof(KerberosServiceName), value); + } + } + string _kerberosServiceName = "postgres"; + + /// + /// The Kerberos realm to be used for authentication. + /// + [Category("Security")] + [Description("The Kerberos realm to be used for authentication.")] + [DisplayName("Include Realm")] + [NpgsqlConnectionStringProperty] + public bool IncludeRealm + { + get => _includeRealm; + set + { + _includeRealm = value; + SetValue(nameof(IncludeRealm), value); + } + } + bool _includeRealm; + + /// + /// Gets or sets a Boolean value that indicates if security-sensitive information, such as the password, is not returned as part of the connection if the connection is open or has ever been in an open state. + /// + [Category("Security")] + [Description("Gets or sets a Boolean value that indicates if security-sensitive information, such as the password, is not returned as part of the connection if the connection is open or has ever been in an open state.")] + [DisplayName("Persist Security Info")] + [NpgsqlConnectionStringProperty] + public bool PersistSecurityInfo + { + get => _persistSecurityInfo; + set + { + _persistSecurityInfo = value; + SetValue(nameof(PersistSecurityInfo), value); + } + } + bool _persistSecurityInfo; + + /// + /// When enabled, parameter values are logged when commands are executed. Defaults to false. + /// + [Category("Security")] + [Description("When enabled, parameter values are logged when commands are executed. Defaults to false.")] + [DisplayName("Log Parameters")] + [NpgsqlConnectionStringProperty] + public bool LogParameters + { + get => _logParameters; + set + { + _logParameters = value; + SetValue(nameof(LogParameters), value); + } + } + bool _logParameters; + + internal const string IncludeExceptionDetailDisplayName = "Include Error Detail"; + + /// + /// When enabled, PostgreSQL error details are included on and + /// . These can contain sensitive data. + /// + [Category("Security")] + [Description("When enabled, PostgreSQL error and notice details are included on PostgresException.Detail and PostgresNotice.Detail. These can contain sensitive data.")] + [DisplayName(IncludeExceptionDetailDisplayName)] + [NpgsqlConnectionStringProperty] + public bool IncludeErrorDetail + { + get => _includeErrorDetail; + set + { + _includeErrorDetail = value; + SetValue(nameof(IncludeErrorDetail), value); + } + } + bool _includeErrorDetail; + + #endregion + + #region Properties - Pooling + + /// + /// Whether connection pooling should be used. + /// + [Category("Pooling")] + [Description("Whether connection pooling should be used.")] + [DisplayName("Pooling")] + [NpgsqlConnectionStringProperty] + [DefaultValue(true)] + public bool Pooling + { + get => _pooling; + set + { + _pooling = value; + SetValue(nameof(Pooling), value); + } + } + bool _pooling; + + /// + /// The minimum connection pool size. + /// + [Category("Pooling")] + [Description("The minimum connection pool size.")] + [DisplayName("Minimum Pool Size")] + [NpgsqlConnectionStringProperty] + [DefaultValue(0)] + public int MinPoolSize + { + get => _minPoolSize; + set + { + if (value < 0) + throw new ArgumentOutOfRangeException(nameof(value), value, "MinPoolSize can't be negative"); + + _minPoolSize = value; + SetValue(nameof(MinPoolSize), value); + } + } + int _minPoolSize; + + /// + /// The maximum connection pool size. + /// + [Category("Pooling")] + [Description("The maximum connection pool size.")] + [DisplayName("Maximum Pool Size")] + [NpgsqlConnectionStringProperty] + [DefaultValue(100)] + public int MaxPoolSize + { + get => _maxPoolSize; + set + { + if (value < 0) + throw new ArgumentOutOfRangeException(nameof(value), value, "MaxPoolSize can't be negative"); + + _maxPoolSize = value; + SetValue(nameof(MaxPoolSize), value); + } + } + int _maxPoolSize; + + /// + /// The time to wait before closing idle connections in the pool if the count + /// of all connections exceeds MinPoolSize. + /// + /// The time (in seconds) to wait. The default value is 300. + [Category("Pooling")] + [Description("The time to wait before closing unused connections in the pool if the count of all connections exceeds MinPoolSize.")] + [DisplayName("Connection Idle Lifetime")] + [NpgsqlConnectionStringProperty] + [DefaultValue(300)] + public int ConnectionIdleLifetime + { + get => _connectionIdleLifetime; + set + { + _connectionIdleLifetime = value; + SetValue(nameof(ConnectionIdleLifetime), value); + } + } + int _connectionIdleLifetime; + + /// + /// How many seconds the pool waits before attempting to prune idle connections that are beyond + /// idle lifetime (. + /// + /// The interval (in seconds). The default value is 10. + [Category("Pooling")] + [Description("How many seconds the pool waits before attempting to prune idle connections that are beyond idle lifetime.")] + [DisplayName("Connection Pruning Interval")] + [NpgsqlConnectionStringProperty] + [DefaultValue(10)] + public int ConnectionPruningInterval + { + get => _connectionPruningInterval; + set + { + _connectionPruningInterval = value; + SetValue(nameof(ConnectionPruningInterval), value); + } + } + int _connectionPruningInterval; + + /// + /// The total maximum lifetime of connections (in seconds). Connections which have exceeded this value will be + /// destroyed instead of returned from the pool. This is useful in clustered configurations to force load + /// balancing between a running server and a server just brought online. + /// + /// The time (in seconds) to wait, or 0 to to make connections last indefinitely (the default). + [Category("Pooling")] + [Description("The total maximum lifetime of connections (in seconds).")] + [DisplayName("Connection Lifetime")] + [NpgsqlConnectionStringProperty("Load Balance Timeout")] + public int ConnectionLifetime + { + get => _connectionLifetime; + set + { + _connectionLifetime = value; + SetValue(nameof(ConnectionLifetime), value); + } + } + int _connectionLifetime; + + #endregion + + #region Properties - Timeouts + + /// + /// The time to wait (in seconds) while trying to establish a connection before terminating the attempt and generating an error. + /// Defaults to 15 seconds. + /// + [Category("Timeouts")] + [Description("The time to wait (in seconds) while trying to establish a connection before terminating the attempt and generating an error.")] + [DisplayName("Timeout")] + [NpgsqlConnectionStringProperty] + [DefaultValue(DefaultTimeout)] + public int Timeout + { + get => _timeout; + set + { + if (value < 0 || value > NpgsqlConnection.TimeoutLimit) + throw new ArgumentOutOfRangeException(nameof(value), value, "Timeout must be between 0 and " + NpgsqlConnection.TimeoutLimit); + + _timeout = value; + SetValue(nameof(Timeout), value); + } + } + int _timeout; + + internal const int DefaultTimeout = 15; + + /// + /// The time to wait (in seconds) while trying to execute a command before terminating the attempt and generating an error. + /// Defaults to 30 seconds. + /// + [Category("Timeouts")] + [Description("The time to wait (in seconds) while trying to execute a command before terminating the attempt and generating an error. Set to zero for infinity.")] + [DisplayName("Command Timeout")] + [NpgsqlConnectionStringProperty] + [DefaultValue(NpgsqlCommand.DefaultTimeout)] + public int CommandTimeout + { + get => _commandTimeout; + set + { + if (value < 0) + throw new ArgumentOutOfRangeException(nameof(value), value, "CommandTimeout can't be negative"); + + _commandTimeout = value; + SetValue(nameof(CommandTimeout), value); + } + } + int _commandTimeout; + + /// + /// The time to wait (in seconds) while trying to execute a an internal command before terminating the attempt and generating an error. + /// + [Category("Timeouts")] + [Description("The time to wait (in seconds) while trying to execute a an internal command before terminating the attempt and generating an error. -1 uses CommandTimeout, 0 means no timeout.")] + [DisplayName("Internal Command Timeout")] + [NpgsqlConnectionStringProperty] + [DefaultValue(-1)] + public int InternalCommandTimeout + { + get => _internalCommandTimeout; + set + { + if (value != 0 && value != -1 && value < NpgsqlConnector.MinimumInternalCommandTimeout) + throw new ArgumentOutOfRangeException(nameof(value), value, + $"InternalCommandTimeout must be >= {NpgsqlConnector.MinimumInternalCommandTimeout}, 0 (infinite) or -1 (use CommandTimeout)"); + + _internalCommandTimeout = value; + SetValue(nameof(InternalCommandTimeout), value); + } + } + int _internalCommandTimeout; + + /// + /// The time to wait (in milliseconds) while trying to read a response for a cancellation request for a timed out or cancelled query, before terminating the attempt and generating an error. + /// Zero for infinity, -1 to skip the wait. + /// Defaults to 2000 milliseconds. + /// + [Category("Timeouts")] + [Description("After Command Timeout is reached (or user supplied cancellation token is cancelled) and command cancellation is attempted, Npgsql waits for this additional timeout (in milliseconds) before breaking the connection. Defaults to 2000, set to zero for infinity.")] + [DisplayName("Cancellation Timeout")] + [NpgsqlConnectionStringProperty] + [DefaultValue(2000)] + public int CancellationTimeout + { + get => _cancellationTimeout; + set + { + if (value < -1) + throw new ArgumentOutOfRangeException(nameof(value), value, $"{nameof(CancellationTimeout)} can't less than -1"); + + _cancellationTimeout = value; + SetValue(nameof(CancellationTimeout), value); + } + } + int _cancellationTimeout; + + #endregion + + #region Properties - Failover and load balancing + + /// + /// Determines the preferred PostgreSQL target server type. + /// + [Category("Failover and load balancing")] + [Description("Determines the preferred PostgreSQL target server type.")] + [DisplayName("Target Session Attributes")] + [NpgsqlConnectionStringProperty] + public string? TargetSessionAttributes + { + get => TargetSessionAttributesParsed switch + { + Npgsql.TargetSessionAttributes.Any => "any", + Npgsql.TargetSessionAttributes.Primary => "primary", + Npgsql.TargetSessionAttributes.Standby => "standby", + Npgsql.TargetSessionAttributes.PreferPrimary => "prefer-primary", + Npgsql.TargetSessionAttributes.PreferStandby => "prefer-standby", + Npgsql.TargetSessionAttributes.ReadWrite => "read-write", + Npgsql.TargetSessionAttributes.ReadOnly => "read-only", + null => null, + + _ => throw new ArgumentException($"Unhandled enum value '{TargetSessionAttributesParsed}'") + }; + + set + { + TargetSessionAttributesParsed = value is null ? null : ParseTargetSessionAttributes(value); + SetValue(nameof(TargetSessionAttributes), value); + } + } + + internal TargetSessionAttributes? TargetSessionAttributesParsed { get; private set; } + + internal static TargetSessionAttributes ParseTargetSessionAttributes(string s) + => s switch + { + "any" => Npgsql.TargetSessionAttributes.Any, + "primary" => Npgsql.TargetSessionAttributes.Primary, + "standby" => Npgsql.TargetSessionAttributes.Standby, + "prefer-primary" => Npgsql.TargetSessionAttributes.PreferPrimary, + "prefer-standby" => Npgsql.TargetSessionAttributes.PreferStandby, + "read-write" => Npgsql.TargetSessionAttributes.ReadWrite, + "read-only" => Npgsql.TargetSessionAttributes.ReadOnly, + + _ => throw new ArgumentException($"TargetSessionAttributes contains an invalid value '{s}'") + }; + + /// + /// Enables balancing between multiple hosts by round-robin. + /// + [Category("Failover and load balancing")] + [Description("Enables balancing between multiple hosts by round-robin.")] + [DisplayName("Load Balance Hosts")] + [NpgsqlConnectionStringProperty] + public bool LoadBalanceHosts + { + get => _loadBalanceHosts; + set + { + _loadBalanceHosts = value; + SetValue(nameof(LoadBalanceHosts), value); + } + } + bool _loadBalanceHosts; + + /// + /// Controls for how long the host's cached state will be considered as valid. + /// + [Category("Failover and load balancing")] + [Description("Controls for how long the host's cached state will be considered as valid.")] + [DisplayName("Host Recheck Seconds")] + [DefaultValue(10)] + [NpgsqlConnectionStringProperty] + public int HostRecheckSeconds + { + get => _hostRecheckSeconds; + set + { + if (value < 0) + throw new ArgumentException($"{HostRecheckSeconds} cannot be negative", nameof(HostRecheckSeconds)); + _hostRecheckSeconds = value; + SetValue(nameof(HostRecheckSeconds), value); + } + } + int _hostRecheckSeconds; + + #endregion Properties - Failover and load balancing + + #region Properties - Entity Framework + + /// + /// The database template to specify when creating a database in Entity Framework. If not specified, + /// PostgreSQL defaults to "template1". + /// + /// + /// https://www.postgresql.org/docs/current/static/manage-ag-templatedbs.html + /// + [Category("Entity Framework")] + [Description("The database template to specify when creating a database in Entity Framework. If not specified, PostgreSQL defaults to \"template1\".")] + [DisplayName("EF Template Database")] + [NpgsqlConnectionStringProperty] + public string? EntityTemplateDatabase + { + get => _entityTemplateDatabase; + set + { + _entityTemplateDatabase = value; + SetValue(nameof(EntityTemplateDatabase), value); + } + } + string? _entityTemplateDatabase; + + /// + /// The database admin to specify when creating and dropping a database in Entity Framework. This is needed because + /// Npgsql needs to connect to a database in order to send the create/drop database command. + /// If not specified, defaults to "template1". Check NpgsqlServices.UsingPostgresDBConnection for more information. + /// + [Category("Entity Framework")] + [Description("The database admin to specify when creating and dropping a database in Entity Framework. If not specified, defaults to \"template1\".")] + [DisplayName("EF Admin Database")] + [NpgsqlConnectionStringProperty] + public string? EntityAdminDatabase + { + get => _entityAdminDatabase; + set + { + _entityAdminDatabase = value; + SetValue(nameof(EntityAdminDatabase), value); + } + } + string? _entityAdminDatabase; + + #endregion + + #region Properties - Advanced + + /// + /// The number of seconds of connection inactivity before Npgsql sends a keepalive query. + /// Set to 0 (the default) to disable. + /// + [Category("Advanced")] + [Description("The number of seconds of connection inactivity before Npgsql sends a keepalive query.")] + [DisplayName("Keepalive")] + [NpgsqlConnectionStringProperty] + public int KeepAlive + { + get => _keepAlive; + set + { + if (value < 0) + throw new ArgumentOutOfRangeException(nameof(value), value, "KeepAlive can't be negative"); + + _keepAlive = value; + SetValue(nameof(KeepAlive), value); + } + } + int _keepAlive; + + /// + /// Whether to use TCP keepalive with system defaults if overrides isn't specified. + /// + [Category("Advanced")] + [Description("Whether to use TCP keepalive with system defaults if overrides isn't specified.")] + [DisplayName("TCP Keepalive")] + [NpgsqlConnectionStringProperty] + public bool TcpKeepAlive + { + get => _tcpKeepAlive; + set + { + _tcpKeepAlive = value; + SetValue(nameof(TcpKeepAlive), value); + } + } + bool _tcpKeepAlive; + + /// + /// The number of seconds of connection inactivity before a TCP keepalive query is sent. + /// Use of this option is discouraged, use instead if possible. + /// Set to 0 (the default) to disable. + /// + [Category("Advanced")] + [Description("The number of seconds of connection inactivity before a TCP keepalive query is sent.")] + [DisplayName("TCP Keepalive Time")] + [NpgsqlConnectionStringProperty] + public int TcpKeepAliveTime + { + get => _tcpKeepAliveTime; + set + { + if (value < 0) + throw new ArgumentOutOfRangeException(nameof(value), value, "TcpKeepAliveTime can't be negative"); + + _tcpKeepAliveTime = value; + SetValue(nameof(TcpKeepAliveTime), value); + } + } + int _tcpKeepAliveTime; + + /// + /// The interval, in seconds, between when successive keep-alive packets are sent if no acknowledgement is received. + /// Defaults to the value of . must be non-zero as well. + /// + [Category("Advanced")] + [Description("The interval, in seconds, between when successive keep-alive packets are sent if no acknowledgement is received.")] + [DisplayName("TCP Keepalive Interval")] + [NpgsqlConnectionStringProperty] + public int TcpKeepAliveInterval + { + get => _tcpKeepAliveInterval; + set + { + if (value < 0) + throw new ArgumentOutOfRangeException(nameof(value), value, "TcpKeepAliveInterval can't be negative"); + + _tcpKeepAliveInterval = value; + SetValue(nameof(TcpKeepAliveInterval), value); + } + } + int _tcpKeepAliveInterval; + + /// + /// Determines the size of the internal buffer Npgsql uses when reading. Increasing may improve performance if transferring large values from the database. + /// + [Category("Advanced")] + [Description("Determines the size of the internal buffer Npgsql uses when reading. Increasing may improve performance if transferring large values from the database.")] + [DisplayName("Read Buffer Size")] + [NpgsqlConnectionStringProperty] + [DefaultValue(NpgsqlReadBuffer.DefaultSize)] + public int ReadBufferSize + { + get => _readBufferSize; + set + { + _readBufferSize = value; + SetValue(nameof(ReadBufferSize), value); + } + } + int _readBufferSize; + + /// + /// Determines the size of the internal buffer Npgsql uses when writing. Increasing may improve performance if transferring large values to the database. + /// + [Category("Advanced")] + [Description("Determines the size of the internal buffer Npgsql uses when writing. Increasing may improve performance if transferring large values to the database.")] + [DisplayName("Write Buffer Size")] + [NpgsqlConnectionStringProperty] + [DefaultValue(NpgsqlWriteBuffer.DefaultSize)] + public int WriteBufferSize + { + get => _writeBufferSize; + set + { + _writeBufferSize = value; + SetValue(nameof(WriteBufferSize), value); + } + } + int _writeBufferSize; + + /// + /// Determines the size of socket read buffer. + /// + [Category("Advanced")] + [Description("Determines the size of socket receive buffer.")] + [DisplayName("Socket Receive Buffer Size")] + [NpgsqlConnectionStringProperty] + public int SocketReceiveBufferSize + { + get => _socketReceiveBufferSize; + set + { + _socketReceiveBufferSize = value; + SetValue(nameof(SocketReceiveBufferSize), value); + } + } + int _socketReceiveBufferSize; + + /// + /// Determines the size of socket send buffer. + /// + [Category("Advanced")] + [Description("Determines the size of socket send buffer.")] + [DisplayName("Socket Send Buffer Size")] + [NpgsqlConnectionStringProperty] + public int SocketSendBufferSize + { + get => _socketSendBufferSize; + set + { + _socketSendBufferSize = value; + SetValue(nameof(SocketSendBufferSize), value); + } + } + int _socketSendBufferSize; + + /// + /// The maximum number SQL statements that can be automatically prepared at any given point. + /// Beyond this number the least-recently-used statement will be recycled. + /// Zero (the default) disables automatic preparation. + /// + [Category("Advanced")] + [Description("The maximum number SQL statements that can be automatically prepared at any given point. Beyond this number the least-recently-used statement will be recycled. Zero (the default) disables automatic preparation.")] + [DisplayName("Max Auto Prepare")] + [NpgsqlConnectionStringProperty] + public int MaxAutoPrepare + { + get => _maxAutoPrepare; + set + { + if (value < 0) + throw new ArgumentOutOfRangeException(nameof(value), value, $"{nameof(MaxAutoPrepare)} cannot be negative"); + + _maxAutoPrepare = value; + SetValue(nameof(MaxAutoPrepare), value); + } + } + int _maxAutoPrepare; + + /// + /// The minimum number of usages an SQL statement is used before it's automatically prepared. + /// Defaults to 5. + /// + [Category("Advanced")] + [Description("The minimum number of usages an SQL statement is used before it's automatically prepared. Defaults to 5.")] + [DisplayName("Auto Prepare Min Usages")] + [NpgsqlConnectionStringProperty] + [DefaultValue(5)] + public int AutoPrepareMinUsages + { + get => _autoPrepareMinUsages; + set + { + if (value < 1) + throw new ArgumentOutOfRangeException(nameof(value), value, $"{nameof(AutoPrepareMinUsages)} must be 1 or greater"); + + _autoPrepareMinUsages = value; + SetValue(nameof(AutoPrepareMinUsages), value); + } + } + int _autoPrepareMinUsages; + + /// + /// If set to true, a pool connection's state won't be reset when it is closed (improves performance). + /// Do not specify this unless you know what you're doing. + /// + [Category("Advanced")] + [Description("If set to true, a pool connection's state won't be reset when it is closed (improves performance). Do not specify this unless you know what you're doing.")] + [DisplayName("No Reset On Close")] + [NpgsqlConnectionStringProperty] + public bool NoResetOnClose + { + get => _noResetOnClose; + set + { + _noResetOnClose = value; + SetValue(nameof(NoResetOnClose), value); + } + } + bool _noResetOnClose; + + /// + /// Load table composite type definitions, and not just free-standing composite types. + /// + [Category("Advanced")] + [Description("Load table composite type definitions, and not just free-standing composite types.")] + [DisplayName("Load Table Composites")] + [NpgsqlConnectionStringProperty] + public bool LoadTableComposites + { + get => _loadTableComposites; + set + { + _loadTableComposites = value; + SetValue(nameof(LoadTableComposites), value); + } + } + bool _loadTableComposites; + + /// + /// Set the replication mode of the connection + /// + /// + /// This property and its corresponding enum are intentionally kept internal as they + /// should not be set by users or even be visible in their connection strings. + /// Replication connections are a special kind of connection that is encapsulated in + /// + /// and . + /// + [NpgsqlConnectionStringProperty] + [DisplayName("Replication Mode")] + internal ReplicationMode ReplicationMode + { + get => _replicationMode; + set + { + _replicationMode = value; + SetValue(nameof(ReplicationMode), value); + } + } + ReplicationMode _replicationMode; + + /// + /// Set PostgreSQL configuration parameter default values for the connection. + /// + [Category("Advanced")] + [Description("Set PostgreSQL configuration parameter default values for the connection.")] + [DisplayName("Options")] + [NpgsqlConnectionStringProperty] + public string? Options + { + get => _options; + set + { + _options = value; + SetValue(nameof(Options), value); + } + } + + string? _options; + + /// + /// Configure the way arrays of value types are returned when requested as object instances. + /// + [Category("Advanced")] + [Description("Configure the way arrays of value types are returned when requested as object instances.")] + [DisplayName("Array Nullability Mode")] + [NpgsqlConnectionStringProperty] + public ArrayNullabilityMode ArrayNullabilityMode + { + get => _arrayNullabilityMode; + set + { + _arrayNullabilityMode = value; + SetValue(nameof(ArrayNullabilityMode), value); + } + } + + ArrayNullabilityMode _arrayNullabilityMode; + + #endregion + + #region Multiplexing + + /// + /// Enables multiplexing, which allows more efficient use of connections. + /// + [Category("Multiplexing")] + [Description("Enables multiplexing, which allows more efficient use of connections.")] + [DisplayName("Multiplexing")] + [NpgsqlConnectionStringProperty] + [DefaultValue(false)] + public bool Multiplexing + { + get => _multiplexing; + set + { + _multiplexing = value; + SetValue(nameof(Multiplexing), value); + } + } + bool _multiplexing; + + /// + /// When multiplexing is enabled, determines the maximum number of outgoing bytes to buffer before + /// flushing to the network. + /// + [Category("Multiplexing")] + [Description("When multiplexing is enabled, determines the maximum number of outgoing bytes to buffer before " + + "flushing to the network.")] + [DisplayName("Write Coalescing Buffer Threshold Bytes")] + [NpgsqlConnectionStringProperty] + [DefaultValue(1000)] + public int WriteCoalescingBufferThresholdBytes + { + get => _writeCoalescingBufferThresholdBytes; + set + { + _writeCoalescingBufferThresholdBytes = value; + SetValue(nameof(WriteCoalescingBufferThresholdBytes), value); + } + } + int _writeCoalescingBufferThresholdBytes; + + #endregion + + #region Properties - Compatibility + + /// + /// A compatibility mode for special PostgreSQL server types. + /// + [Category("Compatibility")] + [Description("A compatibility mode for special PostgreSQL server types.")] + [DisplayName("Server Compatibility Mode")] + [NpgsqlConnectionStringProperty] + public ServerCompatibilityMode ServerCompatibilityMode + { + get => _serverCompatibilityMode; + set + { + _serverCompatibilityMode = value; + SetValue(nameof(ServerCompatibilityMode), value); + } + } + ServerCompatibilityMode _serverCompatibilityMode; + + #endregion + + #region Properties - Obsolete + + /// + /// Obsolete, see https://www.npgsql.org/doc/release-notes/6.0.html + /// + [Category("Compatibility")] + [Description("Makes MaxValue and MinValue timestamps and dates readable as infinity and negative infinity.")] + [DisplayName("Convert Infinity DateTime")] + [NpgsqlConnectionStringProperty] + [Obsolete("The ConvertInfinityDateTime parameter is no longer supported.")] + public bool ConvertInfinityDateTime + { + get => false; + set => throw new NotSupportedException("The Convert Infinity DateTime parameter is no longer supported; Npgsql 6.0 and above convert min/max values to Infinity by default. See https://www.npgsql.org/doc/types/datetime.html for more details."); + } + + /// + /// Obsolete, see https://www.npgsql.org/doc/release-notes/3.1.html + /// + [Category("Obsolete")] + [Description("Obsolete, see https://www.npgsql.org/doc/release-notes/3.1.html")] + [DisplayName("Continuous Processing")] + [NpgsqlConnectionStringProperty] + [Obsolete("The ContinuousProcessing parameter is no longer supported.")] + public bool ContinuousProcessing + { + get => false; + set => throw new NotSupportedException("The ContinuousProcessing parameter is no longer supported. Please see https://www.npgsql.org/doc/release-notes/3.1.html"); + } + + /// + /// Obsolete, see https://www.npgsql.org/doc/release-notes/3.1.html + /// + [Category("Obsolete")] + [Description("Obsolete, see https://www.npgsql.org/doc/release-notes/3.1.html")] + [DisplayName("Backend Timeouts")] + [NpgsqlConnectionStringProperty] + [Obsolete("The BackendTimeouts parameter is no longer supported")] + public bool BackendTimeouts + { + get => false; + set => throw new NotSupportedException("The BackendTimeouts parameter is no longer supported. Please see https://www.npgsql.org/doc/release-notes/3.1.html"); + } + + /// + /// Obsolete, see https://www.npgsql.org/doc/release-notes/3.0.html + /// + [Category("Obsolete")] + [Description("Obsolete, see https://www.npgsql.org/doc/v/3.0.html")] + [DisplayName("Preload Reader")] + [NpgsqlConnectionStringProperty] + [Obsolete("The PreloadReader parameter is no longer supported")] + public bool PreloadReader + { + get => false; + set => throw new NotSupportedException("The PreloadReader parameter is no longer supported. Please see https://www.npgsql.org/doc/release-notes/3.0.html"); + } + + /// + /// Obsolete, see https://www.npgsql.org/doc/release-notes/3.0.html + /// + [Category("Obsolete")] + [Description("Obsolete, see https://www.npgsql.org/doc/release-notes/3.0.html")] + [DisplayName("Use Extended Types")] + [NpgsqlConnectionStringProperty] + [Obsolete("The UseExtendedTypes parameter is no longer supported")] + public bool UseExtendedTypes + { + get => false; + set => throw new NotSupportedException("The UseExtendedTypes parameter is no longer supported. Please see https://www.npgsql.org/doc/release-notes/3.0.html"); + } + + /// + /// Obsolete, see https://www.npgsql.org/doc/release-notes/4.1.html + /// + [Category("Obsolete")] + [Description("Obsolete, see https://www.npgsql.org/doc/release-notes/4.1.html")] + [DisplayName("Use Ssl Stream")] + [NpgsqlConnectionStringProperty] + [Obsolete("The UseSslStream parameter is no longer supported (always true)")] + public bool UseSslStream + { + get => true; + set => throw new NotSupportedException("The UseSslStream parameter is no longer supported (SslStream is always used). Please see https://www.npgsql.org/doc/release-notes/4.1.html"); + } + + /// + /// Writes connection performance information to performance counters. + /// + [Category("Obsolete")] + [Description("Writes connection performance information to performance counters.")] + [DisplayName("Use Perf Counters")] + [NpgsqlConnectionStringProperty] + [Obsolete("The UsePerfCounters parameter is no longer supported")] + public bool UsePerfCounters + { + get => false; + set => throw new NotSupportedException("The UsePerfCounters parameter is no longer supported. Please see https://www.npgsql.org/doc/release-notes/5.0.html"); + } + + /// + /// Location of a client certificate to be sent to the server. + /// + [Category("Obsolete")] + [Description("Location of a client certificate to be sent to the server.")] + [DisplayName("Client Certificate")] + [NpgsqlConnectionStringProperty] + [Obsolete("Use NpgsqlConnectionStringBuilder.SslKey instead")] + public string? ClientCertificate + { + get => SslKey; + set => SslKey = value; + } + + /// + /// Key for a client certificate to be sent to the server. + /// + [Category("Obsolete")] + [Description("Key for a client certificate to be sent to the server.")] + [DisplayName("Client Certificate Key")] + [NpgsqlConnectionStringProperty] + [Obsolete("Use NpgsqlConnectionStringBuilder.SslPassword instead")] + public string? ClientCertificateKey + { + get => SslPassword; + set => SslPassword = value; + } + + /// + /// When enabled, PostgreSQL error details are included on and + /// . These can contain sensitive data. + /// + [Category("Obsolete")] + [Description("When enabled, PostgreSQL error and notice details are included on PostgresException.Detail and PostgresNotice.Detail. These can contain sensitive data.")] + [DisplayName("Include Error Details")] + [NpgsqlConnectionStringProperty] + [Obsolete("Use NpgsqlConnectionStringBuilder.IncludeErrorDetail instead")] + public bool IncludeErrorDetails + { + get => IncludeErrorDetail; + set => IncludeErrorDetail = value; + } + + #endregion + + #region Misc + + internal void Validate() + { + if (string.IsNullOrWhiteSpace(Host)) + throw new ArgumentException("Host can't be null"); + if (Multiplexing && !Pooling) + throw new ArgumentException("Pooling must be on to use multiplexing"); + if (TrustServerCertificate && SslMode is SslMode.Allow or SslMode.VerifyCA or SslMode.VerifyFull) + throw new ArgumentException(NpgsqlStrings.CannotUseTrustServerCertificate); + } + + internal string ToStringWithoutPassword() + { + var clone = Clone(); + clone.Password = null; + return clone.ToString(); + } + + internal string ConnectionStringForMultipleHosts + { + get + { + var clone = Clone(); + clone[nameof(TargetSessionAttributes)] = null; + return clone.ConnectionString; + } + } + + internal NpgsqlConnectionStringBuilder Clone() => new(ConnectionString); + + internal static bool TrySplitHostPort(ReadOnlySpan originalHost, [NotNullWhen(true)] out string? host, out int port) + { + var portSeparator = originalHost.LastIndexOf(':'); + if (portSeparator != -1) + { + var otherColon = originalHost.Slice(0, portSeparator).LastIndexOf(':'); + var ipv6End = originalHost.LastIndexOf(']'); + if (otherColon == -1 || portSeparator > ipv6End && otherColon < ipv6End) + { + port = originalHost.Slice(portSeparator + 1).ParseInt(); + host = originalHost.Slice(0, portSeparator).ToString(); + return true; + } + } + + port = -1; + host = null; + return false; + } + + internal static bool IsUnixSocket(string host, int port, [NotNullWhen(true)] out string? socketPath, bool replaceForAbstract = true) + { + socketPath = null; + if (string.IsNullOrEmpty(host)) + return false; + + var isPathRooted = Path.IsPathRooted(host); + + if (host[0] == '@') + { + if (replaceForAbstract) + host = $"\0{host.Substring(1)}"; + isPathRooted = true; + } + + if (isPathRooted) + { + socketPath = Path.Combine(host, $".s.PGSQL.{port}"); + return true; + } + + return false; + } + + /// + /// Determines whether the specified object is equal to the current object. + /// + public override bool Equals(object? obj) + => obj is NpgsqlConnectionStringBuilder o && EquivalentTo(o); + + /// + /// Hash function. + /// + /// + public override int GetHashCode() => Host?.GetHashCode() ?? 0; + + #endregion + + #region IDictionary + + /// + /// Gets an containing the keys of the . + /// + public new ICollection Keys => base.Keys.Cast().ToArray()!; + + /// + /// Gets an containing the values in the . + /// + public new ICollection Values => base.Values.Cast().ToArray(); + + /// + /// Copies the elements of the to an Array, starting at a particular Array index. + /// + /// + /// The one-dimensional Array that is the destination of the elements copied from . + /// The Array must have zero-based indexing. + /// + /// + /// The zero-based index in array at which copying begins. + /// + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + foreach (var kv in this) + array[arrayIndex++] = kv; + } + + /// + /// Returns an enumerator that iterates through the . + /// + /// + public IEnumerator> GetEnumerator() + { + foreach (var k in Keys) + yield return new KeyValuePair(k, this[k]); + } + + #endregion IDictionary + + #region ICustomTypeDescriptor + + /// + protected override void GetProperties(Hashtable propertyDescriptors) + { + // Tweak which properties are exposed via TypeDescriptor. This affects the VS DDEX + // provider, for example. + base.GetProperties(propertyDescriptors); + + var toRemove = propertyDescriptors.Values + .Cast() + .Where(d => + !d.Attributes.Cast().Any(a => a is NpgsqlConnectionStringPropertyAttribute) || + d.Attributes.Cast().Any(a => a is ObsoleteAttribute) + ) + .ToList(); + foreach (var o in toRemove) + propertyDescriptors.Remove(o.DisplayName); + } + + #endregion + + internal static readonly string[] EmptyStringArray = new string[0]; +} + +#region Attributes + +/// +/// Marks on which participate in the connection +/// string. Optionally holds a set of synonyms for the property. +/// +[AttributeUsage(AttributeTargets.Property)] +public class NpgsqlConnectionStringPropertyAttribute : Attribute +{ + /// + /// Holds a list of synonyms for the property. + /// + public string[] Synonyms { get; } + + /// + /// Creates a . + /// + public NpgsqlConnectionStringPropertyAttribute() + { + Synonyms = NpgsqlConnectionStringBuilder.EmptyStringArray; + } + + /// + /// Creates a . + /// + public NpgsqlConnectionStringPropertyAttribute(params string[] synonyms) + { + Synonyms = synonyms; + } +} + +#endregion + +#region Enums + +/// +/// An option specified in the connection string that activates special compatibility features. +/// +public enum ServerCompatibilityMode +{ + /// + /// No special server compatibility mode is active + /// + None, + /// + /// The server is an Amazon Redshift instance. + /// + Redshift, + /// + /// The server is doesn't support full type loading from the PostgreSQL catalogs, support the basic set + /// of types via information hardcoded inside Npgsql. + /// + NoTypeLoading, +} + +/// +/// Specifies how to manage SSL. +/// +public enum SslMode +{ + /// + /// SSL is disabled. If the server requires SSL, the connection will fail. + /// + Disable, + /// + /// Prefer non-SSL connections if the server allows them, but allow SSL connections. + /// + Allow, + /// + /// Prefer SSL connections if the server allows them, but allow connections without SSL. + /// + Prefer, + /// + /// Fail the connection if the server doesn't support SSL. + /// + Require, + /// + /// Fail the connection if the server doesn't support SSL. Also verifies server certificate. + /// + VerifyCA, + /// + /// Fail the connection if the server doesn't support SSL. Also verifies server certificate with host's name. + /// + VerifyFull +} + +/// +/// Specifies how the mapping of arrays of +/// value types +/// behaves with respect to nullability when they are requested via an API returning an . +/// +public enum ArrayNullabilityMode +{ + /// + /// Arrays of value types are always returned as non-nullable arrays (e.g. int[]). + /// If the PostgreSQL array contains a NULL value, an exception is thrown. This is the default mode. + /// + Never, + /// + /// Arrays of value types are always returned as nullable arrays (e.g. int?[]). + /// + Always, + /// + /// The type of array that gets returned is determined at runtime. + /// Arrays of value types are returned as non-nullable arrays (e.g. int[]) + /// if the actual instance that gets returned doesn't contain null values + /// and as nullable arrays (e.g. int?[]) if it does. + /// + /// When using this setting, make sure that your code is prepared to the fact + /// that the actual type of array instances returned from APIs like + /// may change on a row by row base. + PerInstance, +} + +/// +/// Specifies whether the connection shall be initialized as a physical or +/// logical replication connection +/// +/// +/// This enum and its corresponding property are intentionally kept internal as they +/// should not be set by users or even be visible in their connection strings. +/// Replication connections are a special kind of connection that is encapsulated in +/// +/// and . +/// +enum ReplicationMode +{ + /// + /// Replication disabled. This is the default + /// + Off, + /// + /// Physical replication enabled + /// + Physical, + /// + /// Logical replication enabled + /// + Logical +} + +/// +/// Specifies server type preference. +/// +enum TargetSessionAttributes : byte +{ + /// + /// Any successful connection is acceptable. + /// + Any = 0, + + /// + /// Session must accept read-write transactions by default (that is, the server must not be in hot standby mode and the + /// default_transaction_read_only parameter must be off). + /// + ReadWrite = 1, + + /// + /// Session must not accept read-write transactions by default (the converse). + /// + ReadOnly = 2, + + /// + /// Server must not be in hot standby mode. + /// + Primary = 3, + + /// + /// Server must be in hot standby mode. + /// + Standby = 4, + + /// + /// First try to find a primary server, but if none of the listed hosts is a primary server, try again in mode. + /// + PreferPrimary = 5, + + /// + /// First try to find a standby server, but if none of the listed hosts is a standby server, try again in mode. + /// + PreferStandby = 6, +} + +#endregion \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlConnectionStringBuilderGenerated.cs b/LibExternal/Npgsql/NpgsqlConnectionStringBuilderGenerated.cs new file mode 100644 index 0000000..ff8dff4 --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlConnectionStringBuilderGenerated.cs @@ -0,0 +1,3835 @@ +namespace Npgsql; + +public sealed partial class NpgsqlConnectionStringBuilder +{ + private partial int Init() + { + // Set the strongly-typed properties to their default values + + Port = 5432; + + Enlist = true; + + Encoding = "UTF8"; + + SslMode = (SslMode)2; + + KerberosServiceName = "postgres"; + + Pooling = true; + + MinPoolSize = 0; + + MaxPoolSize = 100; + + ConnectionIdleLifetime = 300; + + ConnectionPruningInterval = 10; + + Timeout = 15; + + CommandTimeout = 30; + + InternalCommandTimeout = -1; + + CancellationTimeout = 2000; + + HostRecheckSeconds = 10; + + ReadBufferSize = 8192; + + WriteBufferSize = 8192; + + AutoPrepareMinUsages = 5; + + Multiplexing = false; + + WriteCoalescingBufferThresholdBytes = 1000; + + + // Setting the strongly-typed properties here also set the string-based properties in the base class. + // Clear them (default settings = empty connection string) + base.Clear(); + + return 0; + } + + private partial int GeneratedSetter(string keyword, object value) + { + switch (keyword) + { + + case "HOST": + + + Host = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "SERVER": + + + Host = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "PORT": + + + Port = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "DATABASE": + + + Database = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "DB": + + + Database = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "USERNAME": + + + Username = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "USER NAME": + + + Username = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "USERID": + + + Username = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "USER ID": + + + Username = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "UID": + + + Username = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "PASSWORD": + + + Password = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "PSW": + + + Password = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "PWD": + + + Password = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "PASSFILE": + + + Passfile = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "APPLICATION NAME": + + + ApplicationName = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "APPLICATIONNAME": + + + ApplicationName = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "ENLIST": + + + Enlist = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "SEARCH PATH": + + + SearchPath = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "SEARCHPATH": + + + SearchPath = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "CLIENT ENCODING": + + + ClientEncoding = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "CLIENTENCODING": + + + ClientEncoding = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "ENCODING": + + + Encoding = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "TIMEZONE": + + + Timezone = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "SSL MODE": + + + { + SslMode = value is string s + ? (SslMode)Enum.Parse(typeof(SslMode), s, ignoreCase: true) + : (SslMode)Convert.ChangeType(value, typeof(SslMode)); + } + + break; + + case "SSLMODE": + + + { + SslMode = value is string s + ? (SslMode)Enum.Parse(typeof(SslMode), s, ignoreCase: true) + : (SslMode)Convert.ChangeType(value, typeof(SslMode)); + } + + break; + + case "TRUST SERVER CERTIFICATE": + + + TrustServerCertificate = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "TRUSTSERVERCERTIFICATE": + + + TrustServerCertificate = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "SSL CERTIFICATE": + + + SslCertificate = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "SSLCERTIFICATE": + + + SslCertificate = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "SSL KEY": + + + SslKey = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "SSLKEY": + + + SslKey = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "SSL PASSWORD": + + + SslPassword = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "SSLPASSWORD": + + + SslPassword = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "ROOT CERTIFICATE": + + + RootCertificate = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "ROOTCERTIFICATE": + + + RootCertificate = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "CHECK CERTIFICATE REVOCATION": + + + CheckCertificateRevocation = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "CHECKCERTIFICATEREVOCATION": + + + CheckCertificateRevocation = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "INTEGRATED SECURITY": + + + IntegratedSecurity = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "INTEGRATEDSECURITY": + + + IntegratedSecurity = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "KERBEROS SERVICE NAME": + + + KerberosServiceName = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "KERBEROSSERVICENAME": + + + KerberosServiceName = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "KRBSRVNAME": + + + KerberosServiceName = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "INCLUDE REALM": + + + IncludeRealm = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "INCLUDEREALM": + + + IncludeRealm = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "PERSIST SECURITY INFO": + + + PersistSecurityInfo = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "PERSISTSECURITYINFO": + + + PersistSecurityInfo = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "LOG PARAMETERS": + + + LogParameters = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "LOGPARAMETERS": + + + LogParameters = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "INCLUDE ERROR DETAIL": + + + IncludeErrorDetail = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "INCLUDEERRORDETAIL": + + + IncludeErrorDetail = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "POOLING": + + + Pooling = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "MINIMUM POOL SIZE": + + + MinPoolSize = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "MINPOOLSIZE": + + + MinPoolSize = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "MAXIMUM POOL SIZE": + + + MaxPoolSize = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "MAXPOOLSIZE": + + + MaxPoolSize = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "CONNECTION IDLE LIFETIME": + + + ConnectionIdleLifetime = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "CONNECTIONIDLELIFETIME": + + + ConnectionIdleLifetime = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "CONNECTION PRUNING INTERVAL": + + + ConnectionPruningInterval = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "CONNECTIONPRUNINGINTERVAL": + + + ConnectionPruningInterval = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "CONNECTION LIFETIME": + + + ConnectionLifetime = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "CONNECTIONLIFETIME": + + + ConnectionLifetime = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "LOAD BALANCE TIMEOUT": + + + ConnectionLifetime = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "TIMEOUT": + + + Timeout = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "COMMAND TIMEOUT": + + + CommandTimeout = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "COMMANDTIMEOUT": + + + CommandTimeout = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "INTERNAL COMMAND TIMEOUT": + + + InternalCommandTimeout = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "INTERNALCOMMANDTIMEOUT": + + + InternalCommandTimeout = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "CANCELLATION TIMEOUT": + + + CancellationTimeout = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "CANCELLATIONTIMEOUT": + + + CancellationTimeout = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "TARGET SESSION ATTRIBUTES": + + + TargetSessionAttributes = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "TARGETSESSIONATTRIBUTES": + + + TargetSessionAttributes = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "LOAD BALANCE HOSTS": + + + LoadBalanceHosts = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "LOADBALANCEHOSTS": + + + LoadBalanceHosts = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "HOST RECHECK SECONDS": + + + HostRecheckSeconds = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "HOSTRECHECKSECONDS": + + + HostRecheckSeconds = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "EF TEMPLATE DATABASE": + + + EntityTemplateDatabase = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "ENTITYTEMPLATEDATABASE": + + + EntityTemplateDatabase = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "EF ADMIN DATABASE": + + + EntityAdminDatabase = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "ENTITYADMINDATABASE": + + + EntityAdminDatabase = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "KEEPALIVE": + + + KeepAlive = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "TCP KEEPALIVE": + + + TcpKeepAlive = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "TCPKEEPALIVE": + + + TcpKeepAlive = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "TCP KEEPALIVE TIME": + + + TcpKeepAliveTime = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "TCPKEEPALIVETIME": + + + TcpKeepAliveTime = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "TCP KEEPALIVE INTERVAL": + + + TcpKeepAliveInterval = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "TCPKEEPALIVEINTERVAL": + + + TcpKeepAliveInterval = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "READ BUFFER SIZE": + + + ReadBufferSize = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "READBUFFERSIZE": + + + ReadBufferSize = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "WRITE BUFFER SIZE": + + + WriteBufferSize = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "WRITEBUFFERSIZE": + + + WriteBufferSize = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "SOCKET RECEIVE BUFFER SIZE": + + + SocketReceiveBufferSize = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "SOCKETRECEIVEBUFFERSIZE": + + + SocketReceiveBufferSize = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "SOCKET SEND BUFFER SIZE": + + + SocketSendBufferSize = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "SOCKETSENDBUFFERSIZE": + + + SocketSendBufferSize = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "MAX AUTO PREPARE": + + + MaxAutoPrepare = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "MAXAUTOPREPARE": + + + MaxAutoPrepare = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "AUTO PREPARE MIN USAGES": + + + AutoPrepareMinUsages = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "AUTOPREPAREMINUSAGES": + + + AutoPrepareMinUsages = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "NO RESET ON CLOSE": + + + NoResetOnClose = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "NORESETONCLOSE": + + + NoResetOnClose = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "LOAD TABLE COMPOSITES": + + + LoadTableComposites = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "LOADTABLECOMPOSITES": + + + LoadTableComposites = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "REPLICATION MODE": + + + { + ReplicationMode = value is string s + ? (ReplicationMode)Enum.Parse(typeof(ReplicationMode), s, ignoreCase: true) + : (ReplicationMode)Convert.ChangeType(value, typeof(ReplicationMode)); + } + + break; + + case "REPLICATIONMODE": + + + { + ReplicationMode = value is string s + ? (ReplicationMode)Enum.Parse(typeof(ReplicationMode), s, ignoreCase: true) + : (ReplicationMode)Convert.ChangeType(value, typeof(ReplicationMode)); + } + + break; + + case "OPTIONS": + + + Options = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "ARRAY NULLABILITY MODE": + + + { + ArrayNullabilityMode = value is string s + ? (ArrayNullabilityMode)Enum.Parse(typeof(ArrayNullabilityMode), s, ignoreCase: true) + : (ArrayNullabilityMode)Convert.ChangeType(value, typeof(ArrayNullabilityMode)); + } + + break; + + case "ARRAYNULLABILITYMODE": + + + { + ArrayNullabilityMode = value is string s + ? (ArrayNullabilityMode)Enum.Parse(typeof(ArrayNullabilityMode), s, ignoreCase: true) + : (ArrayNullabilityMode)Convert.ChangeType(value, typeof(ArrayNullabilityMode)); + } + + break; + + case "MULTIPLEXING": + + + Multiplexing = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "WRITE COALESCING BUFFER THRESHOLD BYTES": + + + WriteCoalescingBufferThresholdBytes = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "WRITECOALESCINGBUFFERTHRESHOLDBYTES": + + + WriteCoalescingBufferThresholdBytes = (Int32)Convert.ChangeType(value, typeof(Int32)); + + break; + + case "SERVER COMPATIBILITY MODE": + + + { + ServerCompatibilityMode = value is string s + ? (ServerCompatibilityMode)Enum.Parse(typeof(ServerCompatibilityMode), s, ignoreCase: true) + : (ServerCompatibilityMode)Convert.ChangeType(value, typeof(ServerCompatibilityMode)); + } + + break; + + case "SERVERCOMPATIBILITYMODE": + + + { + ServerCompatibilityMode = value is string s + ? (ServerCompatibilityMode)Enum.Parse(typeof(ServerCompatibilityMode), s, ignoreCase: true) + : (ServerCompatibilityMode)Convert.ChangeType(value, typeof(ServerCompatibilityMode)); + } + + break; + + case "CONVERT INFINITY DATETIME": + + + ConvertInfinityDateTime = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "CONVERTINFINITYDATETIME": + + + ConvertInfinityDateTime = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "CONTINUOUS PROCESSING": + + + ContinuousProcessing = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "CONTINUOUSPROCESSING": + + + ContinuousProcessing = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "BACKEND TIMEOUTS": + + + BackendTimeouts = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "BACKENDTIMEOUTS": + + + BackendTimeouts = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "PRELOAD READER": + + + PreloadReader = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "PRELOADREADER": + + + PreloadReader = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "USE EXTENDED TYPES": + + + UseExtendedTypes = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "USEEXTENDEDTYPES": + + + UseExtendedTypes = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "USE SSL STREAM": + + + UseSslStream = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "USESSLSTREAM": + + + UseSslStream = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "USE PERF COUNTERS": + + + UsePerfCounters = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "USEPERFCOUNTERS": + + + UsePerfCounters = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "CLIENT CERTIFICATE": + + + ClientCertificate = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "CLIENTCERTIFICATE": + + + ClientCertificate = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "CLIENT CERTIFICATE KEY": + + + ClientCertificateKey = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "CLIENTCERTIFICATEKEY": + + + ClientCertificateKey = (String)Convert.ChangeType(value, typeof(String)); + + break; + + case "INCLUDE ERROR DETAILS": + + + IncludeErrorDetails = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + case "INCLUDEERRORDETAILS": + + + IncludeErrorDetails = (Boolean)Convert.ChangeType(value, typeof(Boolean)); + + break; + + + default: + throw new KeyNotFoundException(); + } + + return 0; + } + + private partial bool TryGetValueGenerated(string keyword, out object value) + { + switch (keyword) + { + + case "HOST": + + value = (object)Host ?? ""; + return true; + + case "SERVER": + + value = (object)Host ?? ""; + return true; + + case "PORT": + + value = (object)Port ?? ""; + return true; + + case "DATABASE": + + value = (object)Database ?? ""; + return true; + + case "DB": + + value = (object)Database ?? ""; + return true; + + case "USERNAME": + + value = (object)Username ?? ""; + return true; + + case "USER NAME": + + value = (object)Username ?? ""; + return true; + + case "USERID": + + value = (object)Username ?? ""; + return true; + + case "USER ID": + + value = (object)Username ?? ""; + return true; + + case "UID": + + value = (object)Username ?? ""; + return true; + + case "PASSWORD": + + value = (object)Password ?? ""; + return true; + + case "PSW": + + value = (object)Password ?? ""; + return true; + + case "PWD": + + value = (object)Password ?? ""; + return true; + + case "PASSFILE": + + value = (object)Passfile ?? ""; + return true; + + case "APPLICATION NAME": + + value = (object)ApplicationName ?? ""; + return true; + + case "APPLICATIONNAME": + + value = (object)ApplicationName ?? ""; + return true; + + case "ENLIST": + + value = (object)Enlist ?? ""; + return true; + + case "SEARCH PATH": + + value = (object)SearchPath ?? ""; + return true; + + case "SEARCHPATH": + + value = (object)SearchPath ?? ""; + return true; + + case "CLIENT ENCODING": + + value = (object)ClientEncoding ?? ""; + return true; + + case "CLIENTENCODING": + + value = (object)ClientEncoding ?? ""; + return true; + + case "ENCODING": + + value = (object)Encoding ?? ""; + return true; + + case "TIMEZONE": + + value = (object)Timezone ?? ""; + return true; + + case "SSL MODE": + + value = (object)SslMode ?? ""; + return true; + + case "SSLMODE": + + value = (object)SslMode ?? ""; + return true; + + case "TRUST SERVER CERTIFICATE": + + value = (object)TrustServerCertificate ?? ""; + return true; + + case "TRUSTSERVERCERTIFICATE": + + value = (object)TrustServerCertificate ?? ""; + return true; + + case "SSL CERTIFICATE": + + value = (object)SslCertificate ?? ""; + return true; + + case "SSLCERTIFICATE": + + value = (object)SslCertificate ?? ""; + return true; + + case "SSL KEY": + + value = (object)SslKey ?? ""; + return true; + + case "SSLKEY": + + value = (object)SslKey ?? ""; + return true; + + case "SSL PASSWORD": + + value = (object)SslPassword ?? ""; + return true; + + case "SSLPASSWORD": + + value = (object)SslPassword ?? ""; + return true; + + case "ROOT CERTIFICATE": + + value = (object)RootCertificate ?? ""; + return true; + + case "ROOTCERTIFICATE": + + value = (object)RootCertificate ?? ""; + return true; + + case "CHECK CERTIFICATE REVOCATION": + + value = (object)CheckCertificateRevocation ?? ""; + return true; + + case "CHECKCERTIFICATEREVOCATION": + + value = (object)CheckCertificateRevocation ?? ""; + return true; + + case "INTEGRATED SECURITY": + + value = (object)IntegratedSecurity ?? ""; + return true; + + case "INTEGRATEDSECURITY": + + value = (object)IntegratedSecurity ?? ""; + return true; + + case "KERBEROS SERVICE NAME": + + value = (object)KerberosServiceName ?? ""; + return true; + + case "KERBEROSSERVICENAME": + + value = (object)KerberosServiceName ?? ""; + return true; + + case "KRBSRVNAME": + + value = (object)KerberosServiceName ?? ""; + return true; + + case "INCLUDE REALM": + + value = (object)IncludeRealm ?? ""; + return true; + + case "INCLUDEREALM": + + value = (object)IncludeRealm ?? ""; + return true; + + case "PERSIST SECURITY INFO": + + value = (object)PersistSecurityInfo ?? ""; + return true; + + case "PERSISTSECURITYINFO": + + value = (object)PersistSecurityInfo ?? ""; + return true; + + case "LOG PARAMETERS": + + value = (object)LogParameters ?? ""; + return true; + + case "LOGPARAMETERS": + + value = (object)LogParameters ?? ""; + return true; + + case "INCLUDE ERROR DETAIL": + + value = (object)IncludeErrorDetail ?? ""; + return true; + + case "INCLUDEERRORDETAIL": + + value = (object)IncludeErrorDetail ?? ""; + return true; + + case "POOLING": + + value = (object)Pooling ?? ""; + return true; + + case "MINIMUM POOL SIZE": + + value = (object)MinPoolSize ?? ""; + return true; + + case "MINPOOLSIZE": + + value = (object)MinPoolSize ?? ""; + return true; + + case "MAXIMUM POOL SIZE": + + value = (object)MaxPoolSize ?? ""; + return true; + + case "MAXPOOLSIZE": + + value = (object)MaxPoolSize ?? ""; + return true; + + case "CONNECTION IDLE LIFETIME": + + value = (object)ConnectionIdleLifetime ?? ""; + return true; + + case "CONNECTIONIDLELIFETIME": + + value = (object)ConnectionIdleLifetime ?? ""; + return true; + + case "CONNECTION PRUNING INTERVAL": + + value = (object)ConnectionPruningInterval ?? ""; + return true; + + case "CONNECTIONPRUNINGINTERVAL": + + value = (object)ConnectionPruningInterval ?? ""; + return true; + + case "CONNECTION LIFETIME": + + value = (object)ConnectionLifetime ?? ""; + return true; + + case "CONNECTIONLIFETIME": + + value = (object)ConnectionLifetime ?? ""; + return true; + + case "LOAD BALANCE TIMEOUT": + + value = (object)ConnectionLifetime ?? ""; + return true; + + case "TIMEOUT": + + value = (object)Timeout ?? ""; + return true; + + case "COMMAND TIMEOUT": + + value = (object)CommandTimeout ?? ""; + return true; + + case "COMMANDTIMEOUT": + + value = (object)CommandTimeout ?? ""; + return true; + + case "INTERNAL COMMAND TIMEOUT": + + value = (object)InternalCommandTimeout ?? ""; + return true; + + case "INTERNALCOMMANDTIMEOUT": + + value = (object)InternalCommandTimeout ?? ""; + return true; + + case "CANCELLATION TIMEOUT": + + value = (object)CancellationTimeout ?? ""; + return true; + + case "CANCELLATIONTIMEOUT": + + value = (object)CancellationTimeout ?? ""; + return true; + + case "TARGET SESSION ATTRIBUTES": + + value = (object)TargetSessionAttributes ?? ""; + return true; + + case "TARGETSESSIONATTRIBUTES": + + value = (object)TargetSessionAttributes ?? ""; + return true; + + case "LOAD BALANCE HOSTS": + + value = (object)LoadBalanceHosts ?? ""; + return true; + + case "LOADBALANCEHOSTS": + + value = (object)LoadBalanceHosts ?? ""; + return true; + + case "HOST RECHECK SECONDS": + + value = (object)HostRecheckSeconds ?? ""; + return true; + + case "HOSTRECHECKSECONDS": + + value = (object)HostRecheckSeconds ?? ""; + return true; + + case "EF TEMPLATE DATABASE": + + value = (object)EntityTemplateDatabase ?? ""; + return true; + + case "ENTITYTEMPLATEDATABASE": + + value = (object)EntityTemplateDatabase ?? ""; + return true; + + case "EF ADMIN DATABASE": + + value = (object)EntityAdminDatabase ?? ""; + return true; + + case "ENTITYADMINDATABASE": + + value = (object)EntityAdminDatabase ?? ""; + return true; + + case "KEEPALIVE": + + value = (object)KeepAlive ?? ""; + return true; + + case "TCP KEEPALIVE": + + value = (object)TcpKeepAlive ?? ""; + return true; + + case "TCPKEEPALIVE": + + value = (object)TcpKeepAlive ?? ""; + return true; + + case "TCP KEEPALIVE TIME": + + value = (object)TcpKeepAliveTime ?? ""; + return true; + + case "TCPKEEPALIVETIME": + + value = (object)TcpKeepAliveTime ?? ""; + return true; + + case "TCP KEEPALIVE INTERVAL": + + value = (object)TcpKeepAliveInterval ?? ""; + return true; + + case "TCPKEEPALIVEINTERVAL": + + value = (object)TcpKeepAliveInterval ?? ""; + return true; + + case "READ BUFFER SIZE": + + value = (object)ReadBufferSize ?? ""; + return true; + + case "READBUFFERSIZE": + + value = (object)ReadBufferSize ?? ""; + return true; + + case "WRITE BUFFER SIZE": + + value = (object)WriteBufferSize ?? ""; + return true; + + case "WRITEBUFFERSIZE": + + value = (object)WriteBufferSize ?? ""; + return true; + + case "SOCKET RECEIVE BUFFER SIZE": + + value = (object)SocketReceiveBufferSize ?? ""; + return true; + + case "SOCKETRECEIVEBUFFERSIZE": + + value = (object)SocketReceiveBufferSize ?? ""; + return true; + + case "SOCKET SEND BUFFER SIZE": + + value = (object)SocketSendBufferSize ?? ""; + return true; + + case "SOCKETSENDBUFFERSIZE": + + value = (object)SocketSendBufferSize ?? ""; + return true; + + case "MAX AUTO PREPARE": + + value = (object)MaxAutoPrepare ?? ""; + return true; + + case "MAXAUTOPREPARE": + + value = (object)MaxAutoPrepare ?? ""; + return true; + + case "AUTO PREPARE MIN USAGES": + + value = (object)AutoPrepareMinUsages ?? ""; + return true; + + case "AUTOPREPAREMINUSAGES": + + value = (object)AutoPrepareMinUsages ?? ""; + return true; + + case "NO RESET ON CLOSE": + + value = (object)NoResetOnClose ?? ""; + return true; + + case "NORESETONCLOSE": + + value = (object)NoResetOnClose ?? ""; + return true; + + case "LOAD TABLE COMPOSITES": + + value = (object)LoadTableComposites ?? ""; + return true; + + case "LOADTABLECOMPOSITES": + + value = (object)LoadTableComposites ?? ""; + return true; + + case "REPLICATION MODE": + + value = (object)ReplicationMode ?? ""; + return true; + + case "REPLICATIONMODE": + + value = (object)ReplicationMode ?? ""; + return true; + + case "OPTIONS": + + value = (object)Options ?? ""; + return true; + + case "ARRAY NULLABILITY MODE": + + value = (object)ArrayNullabilityMode ?? ""; + return true; + + case "ARRAYNULLABILITYMODE": + + value = (object)ArrayNullabilityMode ?? ""; + return true; + + case "MULTIPLEXING": + + value = (object)Multiplexing ?? ""; + return true; + + case "WRITE COALESCING BUFFER THRESHOLD BYTES": + + value = (object)WriteCoalescingBufferThresholdBytes ?? ""; + return true; + + case "WRITECOALESCINGBUFFERTHRESHOLDBYTES": + + value = (object)WriteCoalescingBufferThresholdBytes ?? ""; + return true; + + case "SERVER COMPATIBILITY MODE": + + value = (object)ServerCompatibilityMode ?? ""; + return true; + + case "SERVERCOMPATIBILITYMODE": + + value = (object)ServerCompatibilityMode ?? ""; + return true; + + case "CONVERT INFINITY DATETIME": + + value = (object)ConvertInfinityDateTime ?? ""; + return true; + + case "CONVERTINFINITYDATETIME": + + value = (object)ConvertInfinityDateTime ?? ""; + return true; + + case "CONTINUOUS PROCESSING": + + value = (object)ContinuousProcessing ?? ""; + return true; + + case "CONTINUOUSPROCESSING": + + value = (object)ContinuousProcessing ?? ""; + return true; + + case "BACKEND TIMEOUTS": + + value = (object)BackendTimeouts ?? ""; + return true; + + case "BACKENDTIMEOUTS": + + value = (object)BackendTimeouts ?? ""; + return true; + + case "PRELOAD READER": + + value = (object)PreloadReader ?? ""; + return true; + + case "PRELOADREADER": + + value = (object)PreloadReader ?? ""; + return true; + + case "USE EXTENDED TYPES": + + value = (object)UseExtendedTypes ?? ""; + return true; + + case "USEEXTENDEDTYPES": + + value = (object)UseExtendedTypes ?? ""; + return true; + + case "USE SSL STREAM": + + value = (object)UseSslStream ?? ""; + return true; + + case "USESSLSTREAM": + + value = (object)UseSslStream ?? ""; + return true; + + case "USE PERF COUNTERS": + + value = (object)UsePerfCounters ?? ""; + return true; + + case "USEPERFCOUNTERS": + + value = (object)UsePerfCounters ?? ""; + return true; + + case "CLIENT CERTIFICATE": + + value = (object)ClientCertificate ?? ""; + return true; + + case "CLIENTCERTIFICATE": + + value = (object)ClientCertificate ?? ""; + return true; + + case "CLIENT CERTIFICATE KEY": + + value = (object)ClientCertificateKey ?? ""; + return true; + + case "CLIENTCERTIFICATEKEY": + + value = (object)ClientCertificateKey ?? ""; + return true; + + case "INCLUDE ERROR DETAILS": + + value = (object)IncludeErrorDetails ?? ""; + return true; + + case "INCLUDEERRORDETAILS": + + value = (object)IncludeErrorDetails ?? ""; + return true; + + } + + value = null; + return false; + } + + private partial bool ContainsKeyGenerated(string keyword) + => keyword switch + { + + "HOST" => true, + + "SERVER" => true, + + "PORT" => true, + + "DATABASE" => true, + + "DB" => true, + + "USERNAME" => true, + + "USER NAME" => true, + + "USERID" => true, + + "USER ID" => true, + + "UID" => true, + + "PASSWORD" => true, + + "PSW" => true, + + "PWD" => true, + + "PASSFILE" => true, + + "APPLICATION NAME" => true, + + "APPLICATIONNAME" => true, + + "ENLIST" => true, + + "SEARCH PATH" => true, + + "SEARCHPATH" => true, + + "CLIENT ENCODING" => true, + + "CLIENTENCODING" => true, + + "ENCODING" => true, + + "TIMEZONE" => true, + + "SSL MODE" => true, + + "SSLMODE" => true, + + "TRUST SERVER CERTIFICATE" => true, + + "TRUSTSERVERCERTIFICATE" => true, + + "SSL CERTIFICATE" => true, + + "SSLCERTIFICATE" => true, + + "SSL KEY" => true, + + "SSLKEY" => true, + + "SSL PASSWORD" => true, + + "SSLPASSWORD" => true, + + "ROOT CERTIFICATE" => true, + + "ROOTCERTIFICATE" => true, + + "CHECK CERTIFICATE REVOCATION" => true, + + "CHECKCERTIFICATEREVOCATION" => true, + + "INTEGRATED SECURITY" => true, + + "INTEGRATEDSECURITY" => true, + + "KERBEROS SERVICE NAME" => true, + + "KERBEROSSERVICENAME" => true, + + "KRBSRVNAME" => true, + + "INCLUDE REALM" => true, + + "INCLUDEREALM" => true, + + "PERSIST SECURITY INFO" => true, + + "PERSISTSECURITYINFO" => true, + + "LOG PARAMETERS" => true, + + "LOGPARAMETERS" => true, + + "INCLUDE ERROR DETAIL" => true, + + "INCLUDEERRORDETAIL" => true, + + "POOLING" => true, + + "MINIMUM POOL SIZE" => true, + + "MINPOOLSIZE" => true, + + "MAXIMUM POOL SIZE" => true, + + "MAXPOOLSIZE" => true, + + "CONNECTION IDLE LIFETIME" => true, + + "CONNECTIONIDLELIFETIME" => true, + + "CONNECTION PRUNING INTERVAL" => true, + + "CONNECTIONPRUNINGINTERVAL" => true, + + "CONNECTION LIFETIME" => true, + + "CONNECTIONLIFETIME" => true, + + "LOAD BALANCE TIMEOUT" => true, + + "TIMEOUT" => true, + + "COMMAND TIMEOUT" => true, + + "COMMANDTIMEOUT" => true, + + "INTERNAL COMMAND TIMEOUT" => true, + + "INTERNALCOMMANDTIMEOUT" => true, + + "CANCELLATION TIMEOUT" => true, + + "CANCELLATIONTIMEOUT" => true, + + "TARGET SESSION ATTRIBUTES" => true, + + "TARGETSESSIONATTRIBUTES" => true, + + "LOAD BALANCE HOSTS" => true, + + "LOADBALANCEHOSTS" => true, + + "HOST RECHECK SECONDS" => true, + + "HOSTRECHECKSECONDS" => true, + + "EF TEMPLATE DATABASE" => true, + + "ENTITYTEMPLATEDATABASE" => true, + + "EF ADMIN DATABASE" => true, + + "ENTITYADMINDATABASE" => true, + + "KEEPALIVE" => true, + + "TCP KEEPALIVE" => true, + + "TCPKEEPALIVE" => true, + + "TCP KEEPALIVE TIME" => true, + + "TCPKEEPALIVETIME" => true, + + "TCP KEEPALIVE INTERVAL" => true, + + "TCPKEEPALIVEINTERVAL" => true, + + "READ BUFFER SIZE" => true, + + "READBUFFERSIZE" => true, + + "WRITE BUFFER SIZE" => true, + + "WRITEBUFFERSIZE" => true, + + "SOCKET RECEIVE BUFFER SIZE" => true, + + "SOCKETRECEIVEBUFFERSIZE" => true, + + "SOCKET SEND BUFFER SIZE" => true, + + "SOCKETSENDBUFFERSIZE" => true, + + "MAX AUTO PREPARE" => true, + + "MAXAUTOPREPARE" => true, + + "AUTO PREPARE MIN USAGES" => true, + + "AUTOPREPAREMINUSAGES" => true, + + "NO RESET ON CLOSE" => true, + + "NORESETONCLOSE" => true, + + "LOAD TABLE COMPOSITES" => true, + + "LOADTABLECOMPOSITES" => true, + + "REPLICATION MODE" => true, + + "REPLICATIONMODE" => true, + + "OPTIONS" => true, + + "ARRAY NULLABILITY MODE" => true, + + "ARRAYNULLABILITYMODE" => true, + + "MULTIPLEXING" => true, + + "WRITE COALESCING BUFFER THRESHOLD BYTES" => true, + + "WRITECOALESCINGBUFFERTHRESHOLDBYTES" => true, + + "SERVER COMPATIBILITY MODE" => true, + + "SERVERCOMPATIBILITYMODE" => true, + + "CONVERT INFINITY DATETIME" => true, + + "CONVERTINFINITYDATETIME" => true, + + "CONTINUOUS PROCESSING" => true, + + "CONTINUOUSPROCESSING" => true, + + "BACKEND TIMEOUTS" => true, + + "BACKENDTIMEOUTS" => true, + + "PRELOAD READER" => true, + + "PRELOADREADER" => true, + + "USE EXTENDED TYPES" => true, + + "USEEXTENDEDTYPES" => true, + + "USE SSL STREAM" => true, + + "USESSLSTREAM" => true, + + "USE PERF COUNTERS" => true, + + "USEPERFCOUNTERS" => true, + + "CLIENT CERTIFICATE" => true, + + "CLIENTCERTIFICATE" => true, + + "CLIENT CERTIFICATE KEY" => true, + + "CLIENTCERTIFICATEKEY" => true, + + "INCLUDE ERROR DETAILS" => true, + + "INCLUDEERRORDETAILS" => true, + + + _ => false + }; + + private partial bool RemoveGenerated(string keyword) + { + switch (keyword) + { + + case "HOST": + { + + var removed = base.ContainsKey("Host"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + Host = default; + + base.Remove("Host"); + return removed; + } + + case "SERVER": + { + + var removed = base.ContainsKey("Host"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + Host = default; + + base.Remove("Host"); + return removed; + } + + case "PORT": + { + + var removed = base.ContainsKey("Port"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + Port = 5432; + + base.Remove("Port"); + return removed; + } + + case "DATABASE": + { + + var removed = base.ContainsKey("Database"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + Database = default; + + base.Remove("Database"); + return removed; + } + + case "DB": + { + + var removed = base.ContainsKey("Database"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + Database = default; + + base.Remove("Database"); + return removed; + } + + case "USERNAME": + { + + var removed = base.ContainsKey("Username"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + Username = default; + + base.Remove("Username"); + return removed; + } + + case "USER NAME": + { + + var removed = base.ContainsKey("Username"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + Username = default; + + base.Remove("Username"); + return removed; + } + + case "USERID": + { + + var removed = base.ContainsKey("Username"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + Username = default; + + base.Remove("Username"); + return removed; + } + + case "USER ID": + { + + var removed = base.ContainsKey("Username"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + Username = default; + + base.Remove("Username"); + return removed; + } + + case "UID": + { + + var removed = base.ContainsKey("Username"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + Username = default; + + base.Remove("Username"); + return removed; + } + + case "PASSWORD": + { + + var removed = base.ContainsKey("Password"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + Password = default; + + base.Remove("Password"); + return removed; + } + + case "PSW": + { + + var removed = base.ContainsKey("Password"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + Password = default; + + base.Remove("Password"); + return removed; + } + + case "PWD": + { + + var removed = base.ContainsKey("Password"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + Password = default; + + base.Remove("Password"); + return removed; + } + + case "PASSFILE": + { + + var removed = base.ContainsKey("Passfile"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + Passfile = default; + + base.Remove("Passfile"); + return removed; + } + + case "APPLICATION NAME": + { + + var removed = base.ContainsKey("Application Name"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ApplicationName = default; + + base.Remove("Application Name"); + return removed; + } + + case "APPLICATIONNAME": + { + + var removed = base.ContainsKey("Application Name"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ApplicationName = default; + + base.Remove("Application Name"); + return removed; + } + + case "ENLIST": + { + + var removed = base.ContainsKey("Enlist"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + Enlist = true; + + base.Remove("Enlist"); + return removed; + } + + case "SEARCH PATH": + { + + var removed = base.ContainsKey("Search Path"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + SearchPath = default; + + base.Remove("Search Path"); + return removed; + } + + case "SEARCHPATH": + { + + var removed = base.ContainsKey("Search Path"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + SearchPath = default; + + base.Remove("Search Path"); + return removed; + } + + case "CLIENT ENCODING": + { + + var removed = base.ContainsKey("Client Encoding"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ClientEncoding = default; + + base.Remove("Client Encoding"); + return removed; + } + + case "CLIENTENCODING": + { + + var removed = base.ContainsKey("Client Encoding"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ClientEncoding = default; + + base.Remove("Client Encoding"); + return removed; + } + + case "ENCODING": + { + + var removed = base.ContainsKey("Encoding"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + Encoding = "UTF8"; + + base.Remove("Encoding"); + return removed; + } + + case "TIMEZONE": + { + + var removed = base.ContainsKey("Timezone"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + Timezone = default; + + base.Remove("Timezone"); + return removed; + } + + case "SSL MODE": + { + + var removed = base.ContainsKey("SSL Mode"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + SslMode = (SslMode)2; + + base.Remove("SSL Mode"); + return removed; + } + + case "SSLMODE": + { + + var removed = base.ContainsKey("SSL Mode"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + SslMode = (SslMode)2; + + base.Remove("SSL Mode"); + return removed; + } + + case "TRUST SERVER CERTIFICATE": + { + + var removed = base.ContainsKey("Trust Server Certificate"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + TrustServerCertificate = default; + + base.Remove("Trust Server Certificate"); + return removed; + } + + case "TRUSTSERVERCERTIFICATE": + { + + var removed = base.ContainsKey("Trust Server Certificate"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + TrustServerCertificate = default; + + base.Remove("Trust Server Certificate"); + return removed; + } + + case "SSL CERTIFICATE": + { + + var removed = base.ContainsKey("SSL Certificate"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + SslCertificate = default; + + base.Remove("SSL Certificate"); + return removed; + } + + case "SSLCERTIFICATE": + { + + var removed = base.ContainsKey("SSL Certificate"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + SslCertificate = default; + + base.Remove("SSL Certificate"); + return removed; + } + + case "SSL KEY": + { + + var removed = base.ContainsKey("SSL Key"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + SslKey = default; + + base.Remove("SSL Key"); + return removed; + } + + case "SSLKEY": + { + + var removed = base.ContainsKey("SSL Key"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + SslKey = default; + + base.Remove("SSL Key"); + return removed; + } + + case "SSL PASSWORD": + { + + var removed = base.ContainsKey("SSL Password"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + SslPassword = default; + + base.Remove("SSL Password"); + return removed; + } + + case "SSLPASSWORD": + { + + var removed = base.ContainsKey("SSL Password"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + SslPassword = default; + + base.Remove("SSL Password"); + return removed; + } + + case "ROOT CERTIFICATE": + { + + var removed = base.ContainsKey("Root Certificate"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + RootCertificate = default; + + base.Remove("Root Certificate"); + return removed; + } + + case "ROOTCERTIFICATE": + { + + var removed = base.ContainsKey("Root Certificate"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + RootCertificate = default; + + base.Remove("Root Certificate"); + return removed; + } + + case "CHECK CERTIFICATE REVOCATION": + { + + var removed = base.ContainsKey("Check Certificate Revocation"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + CheckCertificateRevocation = default; + + base.Remove("Check Certificate Revocation"); + return removed; + } + + case "CHECKCERTIFICATEREVOCATION": + { + + var removed = base.ContainsKey("Check Certificate Revocation"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + CheckCertificateRevocation = default; + + base.Remove("Check Certificate Revocation"); + return removed; + } + + case "INTEGRATED SECURITY": + { + + var removed = base.ContainsKey("Integrated Security"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + IntegratedSecurity = default; + + base.Remove("Integrated Security"); + return removed; + } + + case "INTEGRATEDSECURITY": + { + + var removed = base.ContainsKey("Integrated Security"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + IntegratedSecurity = default; + + base.Remove("Integrated Security"); + return removed; + } + + case "KERBEROS SERVICE NAME": + { + + var removed = base.ContainsKey("Kerberos Service Name"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + KerberosServiceName = "postgres"; + + base.Remove("Kerberos Service Name"); + return removed; + } + + case "KERBEROSSERVICENAME": + { + + var removed = base.ContainsKey("Kerberos Service Name"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + KerberosServiceName = "postgres"; + + base.Remove("Kerberos Service Name"); + return removed; + } + + case "KRBSRVNAME": + { + + var removed = base.ContainsKey("Kerberos Service Name"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + KerberosServiceName = "postgres"; + + base.Remove("Kerberos Service Name"); + return removed; + } + + case "INCLUDE REALM": + { + + var removed = base.ContainsKey("Include Realm"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + IncludeRealm = default; + + base.Remove("Include Realm"); + return removed; + } + + case "INCLUDEREALM": + { + + var removed = base.ContainsKey("Include Realm"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + IncludeRealm = default; + + base.Remove("Include Realm"); + return removed; + } + + case "PERSIST SECURITY INFO": + { + + var removed = base.ContainsKey("Persist Security Info"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + PersistSecurityInfo = default; + + base.Remove("Persist Security Info"); + return removed; + } + + case "PERSISTSECURITYINFO": + { + + var removed = base.ContainsKey("Persist Security Info"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + PersistSecurityInfo = default; + + base.Remove("Persist Security Info"); + return removed; + } + + case "LOG PARAMETERS": + { + + var removed = base.ContainsKey("Log Parameters"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + LogParameters = default; + + base.Remove("Log Parameters"); + return removed; + } + + case "LOGPARAMETERS": + { + + var removed = base.ContainsKey("Log Parameters"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + LogParameters = default; + + base.Remove("Log Parameters"); + return removed; + } + + case "INCLUDE ERROR DETAIL": + { + + var removed = base.ContainsKey("Include Error Detail"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + IncludeErrorDetail = default; + + base.Remove("Include Error Detail"); + return removed; + } + + case "INCLUDEERRORDETAIL": + { + + var removed = base.ContainsKey("Include Error Detail"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + IncludeErrorDetail = default; + + base.Remove("Include Error Detail"); + return removed; + } + + case "POOLING": + { + + var removed = base.ContainsKey("Pooling"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + Pooling = true; + + base.Remove("Pooling"); + return removed; + } + + case "MINIMUM POOL SIZE": + { + + var removed = base.ContainsKey("Minimum Pool Size"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + MinPoolSize = 0; + + base.Remove("Minimum Pool Size"); + return removed; + } + + case "MINPOOLSIZE": + { + + var removed = base.ContainsKey("Minimum Pool Size"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + MinPoolSize = 0; + + base.Remove("Minimum Pool Size"); + return removed; + } + + case "MAXIMUM POOL SIZE": + { + + var removed = base.ContainsKey("Maximum Pool Size"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + MaxPoolSize = 100; + + base.Remove("Maximum Pool Size"); + return removed; + } + + case "MAXPOOLSIZE": + { + + var removed = base.ContainsKey("Maximum Pool Size"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + MaxPoolSize = 100; + + base.Remove("Maximum Pool Size"); + return removed; + } + + case "CONNECTION IDLE LIFETIME": + { + + var removed = base.ContainsKey("Connection Idle Lifetime"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ConnectionIdleLifetime = 300; + + base.Remove("Connection Idle Lifetime"); + return removed; + } + + case "CONNECTIONIDLELIFETIME": + { + + var removed = base.ContainsKey("Connection Idle Lifetime"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ConnectionIdleLifetime = 300; + + base.Remove("Connection Idle Lifetime"); + return removed; + } + + case "CONNECTION PRUNING INTERVAL": + { + + var removed = base.ContainsKey("Connection Pruning Interval"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ConnectionPruningInterval = 10; + + base.Remove("Connection Pruning Interval"); + return removed; + } + + case "CONNECTIONPRUNINGINTERVAL": + { + + var removed = base.ContainsKey("Connection Pruning Interval"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ConnectionPruningInterval = 10; + + base.Remove("Connection Pruning Interval"); + return removed; + } + + case "CONNECTION LIFETIME": + { + + var removed = base.ContainsKey("Connection Lifetime"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ConnectionLifetime = default; + + base.Remove("Connection Lifetime"); + return removed; + } + + case "CONNECTIONLIFETIME": + { + + var removed = base.ContainsKey("Connection Lifetime"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ConnectionLifetime = default; + + base.Remove("Connection Lifetime"); + return removed; + } + + case "LOAD BALANCE TIMEOUT": + { + + var removed = base.ContainsKey("Connection Lifetime"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ConnectionLifetime = default; + + base.Remove("Connection Lifetime"); + return removed; + } + + case "TIMEOUT": + { + + var removed = base.ContainsKey("Timeout"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + Timeout = 15; + + base.Remove("Timeout"); + return removed; + } + + case "COMMAND TIMEOUT": + { + + var removed = base.ContainsKey("Command Timeout"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + CommandTimeout = 30; + + base.Remove("Command Timeout"); + return removed; + } + + case "COMMANDTIMEOUT": + { + + var removed = base.ContainsKey("Command Timeout"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + CommandTimeout = 30; + + base.Remove("Command Timeout"); + return removed; + } + + case "INTERNAL COMMAND TIMEOUT": + { + + var removed = base.ContainsKey("Internal Command Timeout"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + InternalCommandTimeout = -1; + + base.Remove("Internal Command Timeout"); + return removed; + } + + case "INTERNALCOMMANDTIMEOUT": + { + + var removed = base.ContainsKey("Internal Command Timeout"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + InternalCommandTimeout = -1; + + base.Remove("Internal Command Timeout"); + return removed; + } + + case "CANCELLATION TIMEOUT": + { + + var removed = base.ContainsKey("Cancellation Timeout"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + CancellationTimeout = 2000; + + base.Remove("Cancellation Timeout"); + return removed; + } + + case "CANCELLATIONTIMEOUT": + { + + var removed = base.ContainsKey("Cancellation Timeout"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + CancellationTimeout = 2000; + + base.Remove("Cancellation Timeout"); + return removed; + } + + case "TARGET SESSION ATTRIBUTES": + { + + var removed = base.ContainsKey("Target Session Attributes"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + TargetSessionAttributes = default; + + base.Remove("Target Session Attributes"); + return removed; + } + + case "TARGETSESSIONATTRIBUTES": + { + + var removed = base.ContainsKey("Target Session Attributes"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + TargetSessionAttributes = default; + + base.Remove("Target Session Attributes"); + return removed; + } + + case "LOAD BALANCE HOSTS": + { + + var removed = base.ContainsKey("Load Balance Hosts"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + LoadBalanceHosts = default; + + base.Remove("Load Balance Hosts"); + return removed; + } + + case "LOADBALANCEHOSTS": + { + + var removed = base.ContainsKey("Load Balance Hosts"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + LoadBalanceHosts = default; + + base.Remove("Load Balance Hosts"); + return removed; + } + + case "HOST RECHECK SECONDS": + { + + var removed = base.ContainsKey("Host Recheck Seconds"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + HostRecheckSeconds = 10; + + base.Remove("Host Recheck Seconds"); + return removed; + } + + case "HOSTRECHECKSECONDS": + { + + var removed = base.ContainsKey("Host Recheck Seconds"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + HostRecheckSeconds = 10; + + base.Remove("Host Recheck Seconds"); + return removed; + } + + case "EF TEMPLATE DATABASE": + { + + var removed = base.ContainsKey("EF Template Database"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + EntityTemplateDatabase = default; + + base.Remove("EF Template Database"); + return removed; + } + + case "ENTITYTEMPLATEDATABASE": + { + + var removed = base.ContainsKey("EF Template Database"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + EntityTemplateDatabase = default; + + base.Remove("EF Template Database"); + return removed; + } + + case "EF ADMIN DATABASE": + { + + var removed = base.ContainsKey("EF Admin Database"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + EntityAdminDatabase = default; + + base.Remove("EF Admin Database"); + return removed; + } + + case "ENTITYADMINDATABASE": + { + + var removed = base.ContainsKey("EF Admin Database"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + EntityAdminDatabase = default; + + base.Remove("EF Admin Database"); + return removed; + } + + case "KEEPALIVE": + { + + var removed = base.ContainsKey("Keepalive"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + KeepAlive = default; + + base.Remove("Keepalive"); + return removed; + } + + case "TCP KEEPALIVE": + { + + var removed = base.ContainsKey("TCP Keepalive"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + TcpKeepAlive = default; + + base.Remove("TCP Keepalive"); + return removed; + } + + case "TCPKEEPALIVE": + { + + var removed = base.ContainsKey("TCP Keepalive"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + TcpKeepAlive = default; + + base.Remove("TCP Keepalive"); + return removed; + } + + case "TCP KEEPALIVE TIME": + { + + var removed = base.ContainsKey("TCP Keepalive Time"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + TcpKeepAliveTime = default; + + base.Remove("TCP Keepalive Time"); + return removed; + } + + case "TCPKEEPALIVETIME": + { + + var removed = base.ContainsKey("TCP Keepalive Time"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + TcpKeepAliveTime = default; + + base.Remove("TCP Keepalive Time"); + return removed; + } + + case "TCP KEEPALIVE INTERVAL": + { + + var removed = base.ContainsKey("TCP Keepalive Interval"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + TcpKeepAliveInterval = default; + + base.Remove("TCP Keepalive Interval"); + return removed; + } + + case "TCPKEEPALIVEINTERVAL": + { + + var removed = base.ContainsKey("TCP Keepalive Interval"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + TcpKeepAliveInterval = default; + + base.Remove("TCP Keepalive Interval"); + return removed; + } + + case "READ BUFFER SIZE": + { + + var removed = base.ContainsKey("Read Buffer Size"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ReadBufferSize = 8192; + + base.Remove("Read Buffer Size"); + return removed; + } + + case "READBUFFERSIZE": + { + + var removed = base.ContainsKey("Read Buffer Size"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ReadBufferSize = 8192; + + base.Remove("Read Buffer Size"); + return removed; + } + + case "WRITE BUFFER SIZE": + { + + var removed = base.ContainsKey("Write Buffer Size"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + WriteBufferSize = 8192; + + base.Remove("Write Buffer Size"); + return removed; + } + + case "WRITEBUFFERSIZE": + { + + var removed = base.ContainsKey("Write Buffer Size"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + WriteBufferSize = 8192; + + base.Remove("Write Buffer Size"); + return removed; + } + + case "SOCKET RECEIVE BUFFER SIZE": + { + + var removed = base.ContainsKey("Socket Receive Buffer Size"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + SocketReceiveBufferSize = default; + + base.Remove("Socket Receive Buffer Size"); + return removed; + } + + case "SOCKETRECEIVEBUFFERSIZE": + { + + var removed = base.ContainsKey("Socket Receive Buffer Size"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + SocketReceiveBufferSize = default; + + base.Remove("Socket Receive Buffer Size"); + return removed; + } + + case "SOCKET SEND BUFFER SIZE": + { + + var removed = base.ContainsKey("Socket Send Buffer Size"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + SocketSendBufferSize = default; + + base.Remove("Socket Send Buffer Size"); + return removed; + } + + case "SOCKETSENDBUFFERSIZE": + { + + var removed = base.ContainsKey("Socket Send Buffer Size"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + SocketSendBufferSize = default; + + base.Remove("Socket Send Buffer Size"); + return removed; + } + + case "MAX AUTO PREPARE": + { + + var removed = base.ContainsKey("Max Auto Prepare"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + MaxAutoPrepare = default; + + base.Remove("Max Auto Prepare"); + return removed; + } + + case "MAXAUTOPREPARE": + { + + var removed = base.ContainsKey("Max Auto Prepare"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + MaxAutoPrepare = default; + + base.Remove("Max Auto Prepare"); + return removed; + } + + case "AUTO PREPARE MIN USAGES": + { + + var removed = base.ContainsKey("Auto Prepare Min Usages"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + AutoPrepareMinUsages = 5; + + base.Remove("Auto Prepare Min Usages"); + return removed; + } + + case "AUTOPREPAREMINUSAGES": + { + + var removed = base.ContainsKey("Auto Prepare Min Usages"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + AutoPrepareMinUsages = 5; + + base.Remove("Auto Prepare Min Usages"); + return removed; + } + + case "NO RESET ON CLOSE": + { + + var removed = base.ContainsKey("No Reset On Close"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + NoResetOnClose = default; + + base.Remove("No Reset On Close"); + return removed; + } + + case "NORESETONCLOSE": + { + + var removed = base.ContainsKey("No Reset On Close"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + NoResetOnClose = default; + + base.Remove("No Reset On Close"); + return removed; + } + + case "LOAD TABLE COMPOSITES": + { + + var removed = base.ContainsKey("Load Table Composites"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + LoadTableComposites = default; + + base.Remove("Load Table Composites"); + return removed; + } + + case "LOADTABLECOMPOSITES": + { + + var removed = base.ContainsKey("Load Table Composites"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + LoadTableComposites = default; + + base.Remove("Load Table Composites"); + return removed; + } + + case "REPLICATION MODE": + { + + var removed = base.ContainsKey("Replication Mode"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ReplicationMode = default; + + base.Remove("Replication Mode"); + return removed; + } + + case "REPLICATIONMODE": + { + + var removed = base.ContainsKey("Replication Mode"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ReplicationMode = default; + + base.Remove("Replication Mode"); + return removed; + } + + case "OPTIONS": + { + + var removed = base.ContainsKey("Options"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + Options = default; + + base.Remove("Options"); + return removed; + } + + case "ARRAY NULLABILITY MODE": + { + + var removed = base.ContainsKey("Array Nullability Mode"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ArrayNullabilityMode = default; + + base.Remove("Array Nullability Mode"); + return removed; + } + + case "ARRAYNULLABILITYMODE": + { + + var removed = base.ContainsKey("Array Nullability Mode"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ArrayNullabilityMode = default; + + base.Remove("Array Nullability Mode"); + return removed; + } + + case "MULTIPLEXING": + { + + var removed = base.ContainsKey("Multiplexing"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + Multiplexing = false; + + base.Remove("Multiplexing"); + return removed; + } + + case "WRITE COALESCING BUFFER THRESHOLD BYTES": + { + + var removed = base.ContainsKey("Write Coalescing Buffer Threshold Bytes"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + WriteCoalescingBufferThresholdBytes = 1000; + + base.Remove("Write Coalescing Buffer Threshold Bytes"); + return removed; + } + + case "WRITECOALESCINGBUFFERTHRESHOLDBYTES": + { + + var removed = base.ContainsKey("Write Coalescing Buffer Threshold Bytes"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + WriteCoalescingBufferThresholdBytes = 1000; + + base.Remove("Write Coalescing Buffer Threshold Bytes"); + return removed; + } + + case "SERVER COMPATIBILITY MODE": + { + + var removed = base.ContainsKey("Server Compatibility Mode"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ServerCompatibilityMode = default; + + base.Remove("Server Compatibility Mode"); + return removed; + } + + case "SERVERCOMPATIBILITYMODE": + { + + var removed = base.ContainsKey("Server Compatibility Mode"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ServerCompatibilityMode = default; + + base.Remove("Server Compatibility Mode"); + return removed; + } + + case "CONVERT INFINITY DATETIME": + { + + var removed = base.ContainsKey("Convert Infinity DateTime"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ConvertInfinityDateTime = default; + + base.Remove("Convert Infinity DateTime"); + return removed; + } + + case "CONVERTINFINITYDATETIME": + { + + var removed = base.ContainsKey("Convert Infinity DateTime"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ConvertInfinityDateTime = default; + + base.Remove("Convert Infinity DateTime"); + return removed; + } + + case "CONTINUOUS PROCESSING": + { + + var removed = base.ContainsKey("Continuous Processing"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ContinuousProcessing = default; + + base.Remove("Continuous Processing"); + return removed; + } + + case "CONTINUOUSPROCESSING": + { + + var removed = base.ContainsKey("Continuous Processing"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ContinuousProcessing = default; + + base.Remove("Continuous Processing"); + return removed; + } + + case "BACKEND TIMEOUTS": + { + + var removed = base.ContainsKey("Backend Timeouts"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + BackendTimeouts = default; + + base.Remove("Backend Timeouts"); + return removed; + } + + case "BACKENDTIMEOUTS": + { + + var removed = base.ContainsKey("Backend Timeouts"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + BackendTimeouts = default; + + base.Remove("Backend Timeouts"); + return removed; + } + + case "PRELOAD READER": + { + + var removed = base.ContainsKey("Preload Reader"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + PreloadReader = default; + + base.Remove("Preload Reader"); + return removed; + } + + case "PRELOADREADER": + { + + var removed = base.ContainsKey("Preload Reader"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + PreloadReader = default; + + base.Remove("Preload Reader"); + return removed; + } + + case "USE EXTENDED TYPES": + { + + var removed = base.ContainsKey("Use Extended Types"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + UseExtendedTypes = default; + + base.Remove("Use Extended Types"); + return removed; + } + + case "USEEXTENDEDTYPES": + { + + var removed = base.ContainsKey("Use Extended Types"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + UseExtendedTypes = default; + + base.Remove("Use Extended Types"); + return removed; + } + + case "USE SSL STREAM": + { + + var removed = base.ContainsKey("Use Ssl Stream"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + UseSslStream = default; + + base.Remove("Use Ssl Stream"); + return removed; + } + + case "USESSLSTREAM": + { + + var removed = base.ContainsKey("Use Ssl Stream"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + UseSslStream = default; + + base.Remove("Use Ssl Stream"); + return removed; + } + + case "USE PERF COUNTERS": + { + + var removed = base.ContainsKey("Use Perf Counters"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + UsePerfCounters = default; + + base.Remove("Use Perf Counters"); + return removed; + } + + case "USEPERFCOUNTERS": + { + + var removed = base.ContainsKey("Use Perf Counters"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + UsePerfCounters = default; + + base.Remove("Use Perf Counters"); + return removed; + } + + case "CLIENT CERTIFICATE": + { + + var removed = base.ContainsKey("Client Certificate"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ClientCertificate = default; + + base.Remove("Client Certificate"); + return removed; + } + + case "CLIENTCERTIFICATE": + { + + var removed = base.ContainsKey("Client Certificate"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ClientCertificate = default; + + base.Remove("Client Certificate"); + return removed; + } + + case "CLIENT CERTIFICATE KEY": + { + + var removed = base.ContainsKey("Client Certificate Key"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ClientCertificateKey = default; + + base.Remove("Client Certificate Key"); + return removed; + } + + case "CLIENTCERTIFICATEKEY": + { + + var removed = base.ContainsKey("Client Certificate Key"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + ClientCertificateKey = default; + + base.Remove("Client Certificate Key"); + return removed; + } + + case "INCLUDE ERROR DETAILS": + { + + var removed = base.ContainsKey("Include Error Details"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + IncludeErrorDetails = default; + + base.Remove("Include Error Details"); + return removed; + } + + case "INCLUDEERRORDETAILS": + { + + var removed = base.ContainsKey("Include Error Details"); + // Note that string property setters call SetValue, which itself calls base.Remove(). + + IncludeErrorDetails = default; + + base.Remove("Include Error Details"); + return removed; + } + + + default: + throw new KeyNotFoundException(); + } + } + + private partial string ToCanonicalKeyword(string keyword) + => keyword switch + { + + "HOST" => "Host", + + "SERVER" => "Host", + + "PORT" => "Port", + + "DATABASE" => "Database", + + "DB" => "Database", + + "USERNAME" => "Username", + + "USER NAME" => "Username", + + "USERID" => "Username", + + "USER ID" => "Username", + + "UID" => "Username", + + "PASSWORD" => "Password", + + "PSW" => "Password", + + "PWD" => "Password", + + "PASSFILE" => "Passfile", + + "APPLICATION NAME" => "Application Name", + + "APPLICATIONNAME" => "Application Name", + + "ENLIST" => "Enlist", + + "SEARCH PATH" => "Search Path", + + "SEARCHPATH" => "Search Path", + + "CLIENT ENCODING" => "Client Encoding", + + "CLIENTENCODING" => "Client Encoding", + + "ENCODING" => "Encoding", + + "TIMEZONE" => "Timezone", + + "SSL MODE" => "SSL Mode", + + "SSLMODE" => "SSL Mode", + + "TRUST SERVER CERTIFICATE" => "Trust Server Certificate", + + "TRUSTSERVERCERTIFICATE" => "Trust Server Certificate", + + "SSL CERTIFICATE" => "SSL Certificate", + + "SSLCERTIFICATE" => "SSL Certificate", + + "SSL KEY" => "SSL Key", + + "SSLKEY" => "SSL Key", + + "SSL PASSWORD" => "SSL Password", + + "SSLPASSWORD" => "SSL Password", + + "ROOT CERTIFICATE" => "Root Certificate", + + "ROOTCERTIFICATE" => "Root Certificate", + + "CHECK CERTIFICATE REVOCATION" => "Check Certificate Revocation", + + "CHECKCERTIFICATEREVOCATION" => "Check Certificate Revocation", + + "INTEGRATED SECURITY" => "Integrated Security", + + "INTEGRATEDSECURITY" => "Integrated Security", + + "KERBEROS SERVICE NAME" => "Kerberos Service Name", + + "KERBEROSSERVICENAME" => "Kerberos Service Name", + + "KRBSRVNAME" => "Kerberos Service Name", + + "INCLUDE REALM" => "Include Realm", + + "INCLUDEREALM" => "Include Realm", + + "PERSIST SECURITY INFO" => "Persist Security Info", + + "PERSISTSECURITYINFO" => "Persist Security Info", + + "LOG PARAMETERS" => "Log Parameters", + + "LOGPARAMETERS" => "Log Parameters", + + "INCLUDE ERROR DETAIL" => "Include Error Detail", + + "INCLUDEERRORDETAIL" => "Include Error Detail", + + "POOLING" => "Pooling", + + "MINIMUM POOL SIZE" => "Minimum Pool Size", + + "MINPOOLSIZE" => "Minimum Pool Size", + + "MAXIMUM POOL SIZE" => "Maximum Pool Size", + + "MAXPOOLSIZE" => "Maximum Pool Size", + + "CONNECTION IDLE LIFETIME" => "Connection Idle Lifetime", + + "CONNECTIONIDLELIFETIME" => "Connection Idle Lifetime", + + "CONNECTION PRUNING INTERVAL" => "Connection Pruning Interval", + + "CONNECTIONPRUNINGINTERVAL" => "Connection Pruning Interval", + + "CONNECTION LIFETIME" => "Connection Lifetime", + + "CONNECTIONLIFETIME" => "Connection Lifetime", + + "LOAD BALANCE TIMEOUT" => "Connection Lifetime", + + "TIMEOUT" => "Timeout", + + "COMMAND TIMEOUT" => "Command Timeout", + + "COMMANDTIMEOUT" => "Command Timeout", + + "INTERNAL COMMAND TIMEOUT" => "Internal Command Timeout", + + "INTERNALCOMMANDTIMEOUT" => "Internal Command Timeout", + + "CANCELLATION TIMEOUT" => "Cancellation Timeout", + + "CANCELLATIONTIMEOUT" => "Cancellation Timeout", + + "TARGET SESSION ATTRIBUTES" => "Target Session Attributes", + + "TARGETSESSIONATTRIBUTES" => "Target Session Attributes", + + "LOAD BALANCE HOSTS" => "Load Balance Hosts", + + "LOADBALANCEHOSTS" => "Load Balance Hosts", + + "HOST RECHECK SECONDS" => "Host Recheck Seconds", + + "HOSTRECHECKSECONDS" => "Host Recheck Seconds", + + "EF TEMPLATE DATABASE" => "EF Template Database", + + "ENTITYTEMPLATEDATABASE" => "EF Template Database", + + "EF ADMIN DATABASE" => "EF Admin Database", + + "ENTITYADMINDATABASE" => "EF Admin Database", + + "KEEPALIVE" => "Keepalive", + + "TCP KEEPALIVE" => "TCP Keepalive", + + "TCPKEEPALIVE" => "TCP Keepalive", + + "TCP KEEPALIVE TIME" => "TCP Keepalive Time", + + "TCPKEEPALIVETIME" => "TCP Keepalive Time", + + "TCP KEEPALIVE INTERVAL" => "TCP Keepalive Interval", + + "TCPKEEPALIVEINTERVAL" => "TCP Keepalive Interval", + + "READ BUFFER SIZE" => "Read Buffer Size", + + "READBUFFERSIZE" => "Read Buffer Size", + + "WRITE BUFFER SIZE" => "Write Buffer Size", + + "WRITEBUFFERSIZE" => "Write Buffer Size", + + "SOCKET RECEIVE BUFFER SIZE" => "Socket Receive Buffer Size", + + "SOCKETRECEIVEBUFFERSIZE" => "Socket Receive Buffer Size", + + "SOCKET SEND BUFFER SIZE" => "Socket Send Buffer Size", + + "SOCKETSENDBUFFERSIZE" => "Socket Send Buffer Size", + + "MAX AUTO PREPARE" => "Max Auto Prepare", + + "MAXAUTOPREPARE" => "Max Auto Prepare", + + "AUTO PREPARE MIN USAGES" => "Auto Prepare Min Usages", + + "AUTOPREPAREMINUSAGES" => "Auto Prepare Min Usages", + + "NO RESET ON CLOSE" => "No Reset On Close", + + "NORESETONCLOSE" => "No Reset On Close", + + "LOAD TABLE COMPOSITES" => "Load Table Composites", + + "LOADTABLECOMPOSITES" => "Load Table Composites", + + "REPLICATION MODE" => "Replication Mode", + + "REPLICATIONMODE" => "Replication Mode", + + "OPTIONS" => "Options", + + "ARRAY NULLABILITY MODE" => "Array Nullability Mode", + + "ARRAYNULLABILITYMODE" => "Array Nullability Mode", + + "MULTIPLEXING" => "Multiplexing", + + "WRITE COALESCING BUFFER THRESHOLD BYTES" => "Write Coalescing Buffer Threshold Bytes", + + "WRITECOALESCINGBUFFERTHRESHOLDBYTES" => "Write Coalescing Buffer Threshold Bytes", + + "SERVER COMPATIBILITY MODE" => "Server Compatibility Mode", + + "SERVERCOMPATIBILITYMODE" => "Server Compatibility Mode", + + "CONVERT INFINITY DATETIME" => "Convert Infinity DateTime", + + "CONVERTINFINITYDATETIME" => "Convert Infinity DateTime", + + "CONTINUOUS PROCESSING" => "Continuous Processing", + + "CONTINUOUSPROCESSING" => "Continuous Processing", + + "BACKEND TIMEOUTS" => "Backend Timeouts", + + "BACKENDTIMEOUTS" => "Backend Timeouts", + + "PRELOAD READER" => "Preload Reader", + + "PRELOADREADER" => "Preload Reader", + + "USE EXTENDED TYPES" => "Use Extended Types", + + "USEEXTENDEDTYPES" => "Use Extended Types", + + "USE SSL STREAM" => "Use Ssl Stream", + + "USESSLSTREAM" => "Use Ssl Stream", + + "USE PERF COUNTERS" => "Use Perf Counters", + + "USEPERFCOUNTERS" => "Use Perf Counters", + + "CLIENT CERTIFICATE" => "Client Certificate", + + "CLIENTCERTIFICATE" => "Client Certificate", + + "CLIENT CERTIFICATE KEY" => "Client Certificate Key", + + "CLIENTCERTIFICATEKEY" => "Client Certificate Key", + + "INCLUDE ERROR DETAILS" => "Include Error Details", + + "INCLUDEERRORDETAILS" => "Include Error Details", + + + _ => throw new KeyNotFoundException() + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlDataAdapter.cs b/LibExternal/Npgsql/NpgsqlDataAdapter.cs new file mode 100644 index 0000000..0c8e082 --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlDataAdapter.cs @@ -0,0 +1,227 @@ +using System; +using System.Data; +using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; + +namespace Npgsql; + +/// +/// Represents the method that handles the events. +/// +/// The source of the event. +/// An that contains the event data. +public delegate void NpgsqlRowUpdatedEventHandler(object sender, NpgsqlRowUpdatedEventArgs e); + +/// +/// Represents the method that handles the events. +/// +/// The source of the event. +/// An that contains the event data. +public delegate void NpgsqlRowUpdatingEventHandler(object sender, NpgsqlRowUpdatingEventArgs e); + +/// +/// This class represents an adapter from many commands: select, update, insert and delete to fill a . +/// +[System.ComponentModel.DesignerCategory("")] +public sealed class NpgsqlDataAdapter : DbDataAdapter +{ + /// + /// Row updated event. + /// + public event NpgsqlRowUpdatedEventHandler? RowUpdated; + + /// + /// Row updating event. + /// + public event NpgsqlRowUpdatingEventHandler? RowUpdating; + + /// + /// Default constructor. + /// + public NpgsqlDataAdapter() {} + + /// + /// Constructor. + /// + /// + public NpgsqlDataAdapter(NpgsqlCommand selectCommand) + => SelectCommand = selectCommand; + + /// + /// Constructor. + /// + /// + /// + public NpgsqlDataAdapter(string selectCommandText, NpgsqlConnection selectConnection) + : this(new NpgsqlCommand(selectCommandText, selectConnection)) {} + + /// + /// Constructor. + /// + /// + /// + public NpgsqlDataAdapter(string selectCommandText, string selectConnectionString) + : this(selectCommandText, new NpgsqlConnection(selectConnectionString)) {} + + /// + /// Create row updated event. + /// + protected override RowUpdatedEventArgs CreateRowUpdatedEvent(DataRow dataRow, IDbCommand? command, + System.Data.StatementType statementType, + DataTableMapping tableMapping) + => new NpgsqlRowUpdatedEventArgs(dataRow, command, statementType, tableMapping); + + /// + /// Create row updating event. + /// + protected override RowUpdatingEventArgs CreateRowUpdatingEvent(DataRow dataRow, IDbCommand? command, + System.Data.StatementType statementType, + DataTableMapping tableMapping) + => new NpgsqlRowUpdatingEventArgs(dataRow, command, statementType, tableMapping); + + /// + /// Raise the RowUpdated event. + /// + /// + protected override void OnRowUpdated(RowUpdatedEventArgs value) + { + //base.OnRowUpdated(value); + if (value is NpgsqlRowUpdatedEventArgs args) + RowUpdated?.Invoke(this, args); + //if (RowUpdated != null && value is NpgsqlRowUpdatedEventArgs args) + // RowUpdated(this, args); + } + + /// + /// Raise the RowUpdating event. + /// + /// + protected override void OnRowUpdating(RowUpdatingEventArgs value) + { + if (value is NpgsqlRowUpdatingEventArgs args) + RowUpdating?.Invoke(this, args); + } + + /// + /// Delete command. + /// + public new NpgsqlCommand? DeleteCommand + { + get => (NpgsqlCommand?)base.DeleteCommand; + set => base.DeleteCommand = value; + } + + /// + /// Select command. + /// + public new NpgsqlCommand? SelectCommand + { + get => (NpgsqlCommand?)base.SelectCommand; + set => base.SelectCommand = value; + } + + /// + /// Update command. + /// + public new NpgsqlCommand? UpdateCommand + { + get => (NpgsqlCommand?)base.UpdateCommand; + set => base.UpdateCommand = value; + } + + /// + /// Insert command. + /// + public new NpgsqlCommand? InsertCommand + { + get => (NpgsqlCommand?)base.InsertCommand; + set => base.InsertCommand = value; + } + + // Temporary implementation, waiting for official support in System.Data via https://github.com/dotnet/runtime/issues/22109 + internal async Task Fill(DataTable dataTable, bool async, CancellationToken cancellationToken = default) + { + var command = SelectCommand; + var activeConnection = command?.Connection ?? throw new InvalidOperationException("Connection required"); + var originalState = ConnectionState.Closed; + + try + { + originalState = activeConnection.State; + if (ConnectionState.Closed == originalState) + await activeConnection.Open(async, cancellationToken); + + var dataReader = await command.ExecuteReader(CommandBehavior.Default, async, cancellationToken); + try + { + return await Fill(dataTable, dataReader, async, cancellationToken); + } + finally + { + if (async) + await dataReader.DisposeAsync(); + else + dataReader.Dispose(); + } + } + finally + { + if (ConnectionState.Closed == originalState) + activeConnection.Close(); + } + } + + async Task Fill(DataTable dataTable, NpgsqlDataReader dataReader, bool async, CancellationToken cancellationToken = default) + { + dataTable.BeginLoadData(); + try + { + var rowsAdded = 0; + var count = dataReader.FieldCount; + var columnCollection = dataTable.Columns; + for (var i = 0; i < count; ++i) + { + var fieldName = dataReader.GetName(i); + if (!columnCollection.Contains(fieldName)) + { + var fieldType = dataReader.GetFieldType(i); + var dataColumn = new DataColumn(fieldName, fieldType); + columnCollection.Add(dataColumn); + } + } + + var values = new object[count]; + + while (async ? await dataReader.ReadAsync(cancellationToken) : dataReader.Read()) + { + dataReader.GetValues(values); + dataTable.LoadDataRow(values, true); + rowsAdded++; + } + return rowsAdded; + } + finally + { + dataTable.EndLoadData(); + } + } +} + +#pragma warning disable 1591 + +public class NpgsqlRowUpdatingEventArgs : RowUpdatingEventArgs +{ + public NpgsqlRowUpdatingEventArgs(DataRow dataRow, IDbCommand? command, System.Data.StatementType statementType, + DataTableMapping tableMapping) + : base(dataRow, command, statementType, tableMapping) {} +} + +public class NpgsqlRowUpdatedEventArgs : RowUpdatedEventArgs +{ + public NpgsqlRowUpdatedEventArgs(DataRow dataRow, IDbCommand? command, System.Data.StatementType statementType, + DataTableMapping tableMapping) + : base(dataRow, command, statementType, tableMapping) {} +} + +#pragma warning restore 1591 \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlDataReader.cs b/LibExternal/Npgsql/NpgsqlDataReader.cs new file mode 100644 index 0000000..be0b6c1 --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlDataReader.cs @@ -0,0 +1,2418 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Data; +using System.Data.Common; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.Internal; +using Npgsql.Internal.TypeHandlers; +using Npgsql.Internal.TypeHandling; +using Npgsql.Logging; +using Npgsql.PostgresTypes; +using Npgsql.Schema; +using Npgsql.Util; +using NpgsqlTypes; +using static Npgsql.Util.Statics; + +#pragma warning disable CA2222 // Do not decrease inherited member visibility +namespace Npgsql; + +/// +/// Reads a forward-only stream of rows from a data source. +/// +#pragma warning disable CA1010 +public sealed class NpgsqlDataReader : DbDataReader, IDbColumnSchemaGenerator +#pragma warning restore CA1010 +{ + internal NpgsqlCommand Command { get; private set; } = default!; + internal NpgsqlConnector Connector { get; } + NpgsqlConnection? _connection; + + /// + /// The behavior of the command with which this reader was executed. + /// + CommandBehavior _behavior; + + /// + /// In multiplexing, this is as the sending is managed in the write multiplexing loop, + /// and does not need to be awaited by the reader. + /// + Task? _sendTask; + + internal ReaderState State = ReaderState.Disposed; + + internal NpgsqlReadBuffer Buffer = default!; + + /// + /// Holds the list of statements being executed by this reader. + /// + List _statements = default!; + + /// + /// The index of the current query resultset we're processing (within a multiquery) + /// + internal int StatementIndex { get; private set; } + + /// + /// The number of columns in the current row + /// + int _numColumns; + + /// + /// Records, for each column, its starting offset and length in the current row. + /// Used only in non-sequential mode. + /// + readonly List<(int Offset, int Length)> _columns = new(); + + /// + /// The index of the column that we're on, i.e. that has already been parsed, is + /// is memory and can be retrieved. Initialized to -1, which means we're on the column + /// count (which comes before the first column). + /// + int _column; + + /// + /// For streaming types (e.g. bytea), holds the byte length of the column. + /// Does not include the length prefix. + /// + internal int ColumnLen; + + internal int PosInColumn; + + /// + /// The position in the buffer at which the current data row message ends. + /// Used only when the row is consumed non-sequentially. + /// + int _dataMsgEnd; + + /// + /// Determines, if we can consume the row non-sequentially. + /// Mostly useful for a sequential mode, when the row is already in the buffer. + /// Should always be true for the non-sequential mode. + /// + bool _canConsumeRowNonSequentially; + + int _charPos; + + /// + /// The RowDescription message for the current resultset being processed + /// + internal RowDescriptionMessage? RowDescription; + + ulong? _recordsAffected; + + /// + /// Whether the current result set has rows + /// + bool _hasRows; + + /// + /// Is raised whenever Close() is called. + /// + public event EventHandler? ReaderClosed; + + bool _isSchemaOnly; + bool _isSequential; + + /// + /// A stream that has been opened on a column. + /// + NpgsqlReadBuffer.ColumnStream? _columnStream; + + /// + /// Used for internal temporary purposes + /// + char[]? _tempCharBuf; + + /// + /// Used to keep track of every unique row this reader object ever traverses. + /// This is used to detect whether nested DbDataReaders are still valid. + /// + internal ulong UniqueRowId; + + internal NpgsqlNestedDataReader? CachedFreeNestedDataReader; + + static readonly NpgsqlLogger Log = NpgsqlLogManager.CreateLogger(nameof(NpgsqlDataReader)); + + internal NpgsqlDataReader(NpgsqlConnector connector) + { + Connector = connector; + } + + internal void Init( + NpgsqlCommand command, CommandBehavior behavior, List statements, Task? sendTask = null) + { + Command = command; + _connection = command.Connection; + _behavior = behavior; + _isSchemaOnly = _behavior.HasFlag(CommandBehavior.SchemaOnly); + _isSequential = _behavior.HasFlag(CommandBehavior.SequentialAccess); + _statements = statements; + StatementIndex = -1; + _sendTask = sendTask; + State = ReaderState.BetweenResults; + _recordsAffected = null; + } + + #region Read + + /// + /// Advances the reader to the next record in a result set. + /// + /// true if there are more rows; otherwise false. + /// + /// The default position of a data reader is before the first record. Therefore, you must call Read to begin accessing data. + /// + public override bool Read() + { + CheckClosedOrDisposed(); + + UniqueRowId++; + var fastRead = TryFastRead(); + return fastRead.HasValue + ? fastRead.Value + : Read(false).GetAwaiter().GetResult(); + } + + /// + /// This is the asynchronous version of + /// + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// A task representing the asynchronous operation. + public override Task ReadAsync(CancellationToken cancellationToken) + { + CheckClosedOrDisposed(); + + UniqueRowId++; + var fastRead = TryFastRead(); + if (fastRead.HasValue) + return fastRead.Value ? PGUtil.TrueTask : PGUtil.FalseTask; + + using (NoSynchronizationContextScope.Enter()) + return Read(true, cancellationToken); + } + + bool? TryFastRead() + { + // This is an optimized execution path that avoids calling any async methods for the (usual) + // case where the next row (or CommandComplete) is already in memory. + + if (_behavior.HasFlag(CommandBehavior.SingleRow)) + return null; + + switch (State) + { + case ReaderState.BeforeResult: + // First Read() after NextResult. Data row has already been processed. + State = ReaderState.InResult; + return true; + case ReaderState.InResult: + if (!_canConsumeRowNonSequentially) + return null; + // We get here, if we're in a non-sequential mode (or the row is already in the buffer) + ConsumeRowNonSequential(); + break; + case ReaderState.BetweenResults: + case ReaderState.Consumed: + case ReaderState.Closed: + case ReaderState.Disposed: + return false; + } + + var readBuf = Connector.ReadBuffer; + if (readBuf.ReadBytesLeft < 5) + return null; + var messageCode = (BackendMessageCode)readBuf.ReadByte(); + var len = readBuf.ReadInt32() - 4; // Transmitted length includes itself + if (messageCode != BackendMessageCode.DataRow || readBuf.ReadBytesLeft < len) + { + readBuf.ReadPosition -= 5; + return null; + } + + var msg = Connector.ParseServerMessage(readBuf, messageCode, len, false)!; + Debug.Assert(msg.Code == BackendMessageCode.DataRow); + ProcessMessage(msg); + return true; + } + + async Task Read(bool async, CancellationToken cancellationToken = default) + { + var registration = Connector.StartNestedCancellableOperation(cancellationToken); + + try + { + switch (State) + { + case ReaderState.BeforeResult: + // First Read() after NextResult. Data row has already been processed. + State = ReaderState.InResult; + return true; + + case ReaderState.InResult: + await ConsumeRow(async); + if (_behavior.HasFlag(CommandBehavior.SingleRow)) + { + // TODO: See optimization proposal in #410 + await Consume(async); + return false; + } + break; + + case ReaderState.BetweenResults: + case ReaderState.Consumed: + case ReaderState.Closed: + case ReaderState.Disposed: + return false; + default: + throw new ArgumentOutOfRangeException(); + } + + var msg2 = await ReadMessage(async); + ProcessMessage(msg2); + return msg2.Code == BackendMessageCode.DataRow; + } + catch + { + State = ReaderState.Consumed; + throw; + } + finally + { + registration.Dispose(); + } + } + + ValueTask ReadMessage(bool async) + { + return _isSequential ? ReadMessageSequential(Connector, async) : Connector.ReadMessage(async); + + static async ValueTask ReadMessageSequential(NpgsqlConnector connector, bool async) + { + var msg = await connector.ReadMessage(async, DataRowLoadingMode.Sequential); + if (msg.Code == BackendMessageCode.DataRow) + { + // Make sure that the datarow's column count is already buffered + await connector.ReadBuffer.Ensure(2, async); + return msg; + } + return msg; + } + } + + #endregion + + #region NextResult + + /// + /// Advances the reader to the next result when reading the results of a batch of statements. + /// + /// + public override bool NextResult() => (_isSchemaOnly ? NextResultSchemaOnly(false) : NextResult(false)) + .GetAwaiter().GetResult(); + + /// + /// This is the asynchronous version of NextResult. + /// + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// A task representing the asynchronous operation. + public override Task NextResultAsync(CancellationToken cancellationToken) + { + using (NoSynchronizationContextScope.Enter()) + return _isSchemaOnly + ? NextResultSchemaOnly(async: true, cancellationToken: cancellationToken) + : NextResult(async: true, cancellationToken: cancellationToken); + } + + /// + /// Internal implementation of NextResult + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + async Task NextResult(bool async, bool isConsuming = false, CancellationToken cancellationToken = default) + { + CheckClosedOrDisposed(); + + IBackendMessage msg; + Debug.Assert(!_isSchemaOnly); + + using var registration = isConsuming ? default : Connector.StartNestedCancellableOperation(cancellationToken); + + try + { + // If we're in the middle of a resultset, consume it + switch (State) + { + case ReaderState.BeforeResult: + case ReaderState.InResult: + await ConsumeRow(async); + while (true) + { + var completedMsg = await Connector.ReadMessage(async, DataRowLoadingMode.Skip); + switch (completedMsg.Code) + { + case BackendMessageCode.CommandComplete: + case BackendMessageCode.EmptyQueryResponse: + ProcessMessage(completedMsg); + break; + default: + continue; + } + + break; + } + + break; + + case ReaderState.BetweenResults: + break; + + case ReaderState.Consumed: + case ReaderState.Closed: + case ReaderState.Disposed: + return false; + default: + throw new ArgumentOutOfRangeException(); + } + + Debug.Assert(State == ReaderState.BetweenResults); + _hasRows = false; + + if (_behavior.HasFlag(CommandBehavior.SingleResult) && StatementIndex == 0 && !isConsuming) + { + await Consume(async); + return false; + } + + // We are now at the end of the previous result set. Read up to the next result set, if any. + // Non-prepared statements receive ParseComplete, BindComplete, DescriptionRow/NoData, + // prepared statements receive only BindComplete + for (StatementIndex++; StatementIndex < _statements.Count; StatementIndex++) + { + var statement = _statements[StatementIndex]; + + if (statement.TryGetPrepared(out var preparedStatement)) + { + Expect(await Connector.ReadMessage(async), Connector); + RowDescription = preparedStatement.Description; + } + else // Non-prepared/preparing flow + { + var pStatement = statement.PreparedStatement; + if (pStatement != null) + { + Debug.Assert(!pStatement.IsPrepared); + if (pStatement.StatementBeingReplaced != null) + { + Expect(await Connector.ReadMessage(async), Connector); + pStatement.StatementBeingReplaced.CompleteUnprepare(); + pStatement.StatementBeingReplaced = null; + } + } + + Expect(await Connector.ReadMessage(async), Connector); + + if (statement.IsPreparing) + { + pStatement!.State = PreparedState.Prepared; + Connector.PreparedStatementManager.NumPrepared++; + statement.IsPreparing = false; + } + + Expect(await Connector.ReadMessage(async), Connector); + msg = await Connector.ReadMessage(async); + + RowDescription = statement.Description = msg.Code switch + { + BackendMessageCode.NoData => null, + + // RowDescription messages are cached on the connector, but if we're auto-preparing, we need to + // clone our own copy which will last beyond the lifetime of this invocation. + BackendMessageCode.RowDescription => pStatement == null + ? (RowDescriptionMessage)msg + : ((RowDescriptionMessage)msg).Clone(), + + _ => throw Connector.UnexpectedMessageReceived(msg.Code) + }; + } + + if (RowDescription == null) + { + // Statement did not generate a resultset (e.g. INSERT) + // Read and process its completion message and move on to the next statement + + msg = await ReadMessage(async); + switch (msg.Code) + { + case BackendMessageCode.CommandComplete: + case BackendMessageCode.EmptyQueryResponse: + break; + case BackendMessageCode.CopyInResponse: + throw Connector.Break(new NotSupportedException( + "COPY isn't supported in regular command execution - see https://www.npgsql.org/doc/copy.html for documentation on COPY with Npgsql. " + + "If you are trying to execute a SQL script created by pg_dump, pass the '--inserts' switch to disable generating COPY statements.")); + default: + throw Connector.UnexpectedMessageReceived(msg.Code); + } + + ProcessMessage(msg); + continue; + } + + if (!Command.IsWrappedByBatch && StatementIndex == 0 && Command.Parameters.HasOutputParameters) + { + // If output parameters are present and this is the first row of the first resultset, + // we must always read it in non-sequential mode because it will be traversed twice (once + // here for the parameters, then as a regular row). + msg = await Connector.ReadMessage(async); + ProcessMessage(msg); + if (msg.Code == BackendMessageCode.DataRow) + PopulateOutputParameters(); + } + else + { + msg = await ReadMessage(async); + ProcessMessage(msg); + } + + switch (msg.Code) + { + case BackendMessageCode.DataRow: + case BackendMessageCode.CommandComplete: + break; + default: + throw Connector.UnexpectedMessageReceived(msg.Code); + } + + return true; + } + + // There are no more queries, we're done. Read the RFQ. + ProcessMessage(Expect(await Connector.ReadMessage(async), Connector)); + RowDescription = null; + return false; + } + catch (Exception e) + { + State = ReaderState.Consumed; + + + // Reference the triggering statement from the exception + if (e is PostgresException postgresException && StatementIndex >= 0 && StatementIndex < _statements.Count) + { + postgresException.BatchCommand = _statements[StatementIndex]; + + // Prevent the command or batch from by recycled (by the connection) when it's disposed. This is important since + // the exception is very likely to escape the using statement of the command, and by that time some other user may + // already be using the recycled instance. + if (!Command.IsWrappedByBatch) + { + Command.IsCached = false; + } + } + + // An error means all subsequent statements were skipped by PostgreSQL. + // If any of them were being prepared, we need to update our bookkeeping to put + // them back in unprepared state. + for (; StatementIndex < _statements.Count; StatementIndex++) + { + var statement = _statements[StatementIndex]; + if (statement.IsPreparing) + { + statement.IsPreparing = false; + statement.PreparedStatement!.AbortPrepare(); + } + } + + throw; + } + } + + void PopulateOutputParameters() + { + // The first row in a stored procedure command that has output parameters needs to be traversed twice - + // once for populating the output parameters and once for the actual result set traversal. So in this + // case we can't be sequential. + Debug.Assert(Command.Parameters.Any(p => p.IsOutputDirection)); + Debug.Assert(StatementIndex == 0); + Debug.Assert(RowDescription != null); + Debug.Assert(State == ReaderState.BeforeResult); + + var currentPosition = Buffer.ReadPosition; + + // Temporarily set our state to InResult to allow us to read the values + State = ReaderState.InResult; + + var pending = new Queue(); + var taken = new List(); + for (var i = 0; i < FieldCount; i++) + { + if (Command.Parameters.TryGetValue(GetName(i), out var p) && p.IsOutputDirection) + { + p.Value = GetValue(i); + taken.Add(p); + } + else + pending.Enqueue(GetValue(i)); + } + + // Not sure where this odd behavior comes from: all output parameters which did not get matched by + // name now get populated with column values which weren't matched. Keeping this for backwards compat, + // opened #2252 for investigation. + foreach (var p in Command.Parameters.Where(p => p.IsOutputDirection && !taken.Contains(p))) + { + if (pending.Count == 0) + break; + p.Value = pending.Dequeue(); + } + + State = ReaderState.BeforeResult; // Set the state back + Buffer.ReadPosition = currentPosition; // Restore position + + _column = -1; + ColumnLen = -1; + PosInColumn = 0; + } + + /// + /// Note that in SchemaOnly mode there are no resultsets, and we read nothing from the backend (all + /// RowDescriptions have already been processed and are available) + /// + async Task NextResultSchemaOnly(bool async, bool isConsuming = false, CancellationToken cancellationToken = default) + { + Debug.Assert(_isSchemaOnly); + + using var registration = isConsuming ? default : Connector.StartNestedCancellableOperation(cancellationToken); + + try + { + switch (State) + { + case ReaderState.BeforeResult: + case ReaderState.InResult: + case ReaderState.BetweenResults: + break; + case ReaderState.Consumed: + case ReaderState.Closed: + case ReaderState.Disposed: + return false; + default: + throw new ArgumentOutOfRangeException(); + } + + for (StatementIndex++; StatementIndex < _statements.Count; StatementIndex++) + { + var statement = _statements[StatementIndex]; + if (statement.TryGetPrepared(out var preparedStatement)) + { + // Row descriptions have already been populated in the statement objects at the + // Prepare phase + RowDescription = preparedStatement.Description; + } + else + { + var pStatement = statement.PreparedStatement; + if (pStatement != null) + { + Debug.Assert(!pStatement.IsPrepared); + if (pStatement.StatementBeingReplaced != null) + { + Expect(await Connector.ReadMessage(async), Connector); + pStatement.StatementBeingReplaced.CompleteUnprepare(); + pStatement.StatementBeingReplaced = null; + } + } + + Expect(await Connector.ReadMessage(async), Connector); + + if (statement.IsPreparing) + { + pStatement!.State = PreparedState.Prepared; + Connector.PreparedStatementManager.NumPrepared++; + statement.IsPreparing = false; + } + + Expect(await Connector.ReadMessage(async), Connector); + var msg = await Connector.ReadMessage(async); + switch (msg.Code) + { + case BackendMessageCode.NoData: + RowDescription = _statements[StatementIndex].Description = null; + break; + case BackendMessageCode.RowDescription: + // We have a resultset + RowDescription = _statements[StatementIndex].Description = (RowDescriptionMessage)msg; + Command.FixupRowDescription(RowDescription, StatementIndex == 0); + break; + default: + throw Connector.UnexpectedMessageReceived(msg.Code); + } + + if (_statements.Skip(StatementIndex + 1).All(x => x.IsPrepared)) + { + // There are no more queries, we're done. Read to the RFQ. + Expect(await Connector.ReadMessage(async), Connector); + } + } + + // Found a resultset + if (RowDescription != null) + return true; + } + + RowDescription = null; + State = ReaderState.Consumed; + + return false; + } + catch (Exception e) + { + State = ReaderState.Consumed; + + // Reference the triggering statement from the exception + if (e is PostgresException postgresException && StatementIndex >= 0 && StatementIndex < _statements.Count) + { + postgresException.BatchCommand = _statements[StatementIndex]; + + // Prevent the command or batch from by recycled (by the connection) when it's disposed. This is important since + // the exception is very likely to escape the using statement of the command, and by that time some other user may + // already be using the recycled instance. + if (!Command.IsWrappedByBatch) + { + Command.IsCached = false; + } + } + + // An error means all subsequent statements were skipped by PostgreSQL. + // If any of them were being prepared, we need to update our bookkeeping to put + // them back in unprepared state. + for (; StatementIndex < _statements.Count; StatementIndex++) + { + var statement = _statements[StatementIndex]; + if (statement.IsPreparing) + { + statement.IsPreparing = false; + statement.PreparedStatement!.AbortPrepare(); + } + } + + throw; + } + } + + #endregion + + #region ProcessMessage + + internal void ProcessMessage(IBackendMessage msg) + { + switch (msg.Code) + { + case BackendMessageCode.DataRow: + ProcessDataRowMessage((DataRowMessage)msg); + return; + + case BackendMessageCode.CommandComplete: + var completed = (CommandCompleteMessage)msg; + switch (completed.StatementType) + { + case StatementType.Update: + case StatementType.Insert: + case StatementType.Delete: + case StatementType.Copy: + case StatementType.Move: + if (!_recordsAffected.HasValue) + _recordsAffected = 0; + _recordsAffected += completed.Rows; + break; + } + + _statements[StatementIndex].ApplyCommandComplete(completed); + goto case BackendMessageCode.EmptyQueryResponse; + + case BackendMessageCode.EmptyQueryResponse: + State = ReaderState.BetweenResults; + return; + + case BackendMessageCode.ReadyForQuery: + State = ReaderState.Consumed; + return; + + default: + throw new Exception("Received unexpected backend message of type " + msg.Code); + } + } + + void ProcessDataRowMessage(DataRowMessage msg) + { + Connector.State = ConnectorState.Fetching; + + // The connector's buffer can actually change between DataRows: + // If a large DataRow exceeding the connector's current read buffer arrives, and we're + // reading in non-sequential mode, a new oversize buffer is allocated. We thus have to + // recapture the connector's buffer on each new DataRow. + // Note that this can happen even in sequential mode, if the row description message is big + // (see #2003) + Buffer = Connector.ReadBuffer; + + _hasRows = true; + _column = -1; + ColumnLen = -1; + PosInColumn = 0; + + // We assume that the row's number of columns is identical to the description's + _numColumns = Buffer.ReadInt16(); + Debug.Assert(_numColumns == RowDescription!.Count, + $"Row's number of columns ({_numColumns}) differs from the row description's ({RowDescription.Count})"); + + _dataMsgEnd = Buffer.ReadPosition + msg.Length - 2; + _canConsumeRowNonSequentially = Buffer.ReadBytesLeft >= msg.Length - 2; + + if (!_isSequential) + { + Debug.Assert(_canConsumeRowNonSequentially); + // Initialize our columns array with the offset and length of the first column + _columns.Clear(); + var len = Buffer.ReadInt32(); + _columns.Add((Buffer.ReadPosition, len)); + } + + switch (State) + { + case ReaderState.BetweenResults: + State = ReaderState.BeforeResult; + break; + case ReaderState.BeforeResult: + State = ReaderState.InResult; + break; + case ReaderState.InResult: + break; + default: + throw Connector.UnexpectedMessageReceived(BackendMessageCode.DataRow); + } + } + + #endregion + + void Cancel() => Connector.PerformPostgresCancellation(); + + /// + /// Gets a value indicating the depth of nesting for the current row. Always returns zero. + /// + public override int Depth => 0; + + /// + /// Gets a value indicating whether the data reader is closed. + /// + public override bool IsClosed => State == ReaderState.Closed || State == ReaderState.Disposed; + + /// + /// Gets the number of rows changed, inserted, or deleted by execution of the SQL statement. + /// + /// + /// The number of rows changed, inserted, or deleted. -1 for SELECT statements; 0 if no rows were affected or the statement failed. + /// + public override int RecordsAffected + => !_recordsAffected.HasValue + ? -1 + : _recordsAffected > int.MaxValue + ? throw new OverflowException( + $"The number of records affected exceeds int.MaxValue. Use {nameof(Rows)}.") + : (int)_recordsAffected; + + /// + /// Gets the number of rows changed, inserted, or deleted by execution of the SQL statement. + /// + /// + /// The number of rows changed, inserted, or deleted. 0 for SELECT statements, if no rows were affected or the statement failed. + /// + public ulong Rows => _recordsAffected ?? 0; + + /// + /// Returns details about each statement that this reader will or has executed. + /// + /// + /// Note that some fields (i.e. rows and oid) are only populated as the reader + /// traverses the result. + /// + /// For commands with multiple queries, this exposes the number of rows affected on + /// a statement-by-statement basis, unlike + /// which exposes an aggregation across all statements. + /// + [Obsolete("Use the new DbBatch API")] + public IReadOnlyList Statements => _statements.AsReadOnly(); + + /// + /// Gets a value that indicates whether this DbDataReader contains one or more rows. + /// + public override bool HasRows + => State switch + { + ReaderState.Closed => throw new InvalidOperationException("Invalid attempt to call HasRows when reader is closed."), + ReaderState.Disposed => throw new ObjectDisposedException(nameof(NpgsqlDataReader)), + _ => _hasRows + }; + + /// + /// Indicates whether the reader is currently positioned on a row, i.e. whether reading a + /// column is possible. + /// This property is different from in that will + /// return true even if attempting to read a column will fail, e.g. before + /// has been called + /// + public bool IsOnRow => State == ReaderState.InResult; + + /// + /// Gets the name of the column, given the zero-based column ordinal. + /// + /// The zero-based column ordinal. + /// The name of the specified column. + public override string GetName(int ordinal) => GetField(ordinal).Name; + + /// + /// Gets the number of columns in the current row. + /// + public override int FieldCount + { + get + { + CheckClosedOrDisposed(); + return RowDescription?.Count ?? 0; + } + } + + #region Cleanup / Dispose + + /// + /// Consumes all result sets for this reader, leaving the connector ready for sending and processing further + /// queries + /// + async Task Consume(bool async) + { + // Skip over the other result sets. Note that this does tally records affected + // from CommandComplete messages, and properly sets state for auto-prepared statements + if (_isSchemaOnly) + while (await NextResultSchemaOnly(async, isConsuming: true)) {} + else + while (await NextResult(async, isConsuming: true)) {} + } + + /// + /// Releases the resources used by the . + /// + protected override void Dispose(bool disposing) + { + try + { + Close(connectionClosing: false, async: false, isDisposing: true).GetAwaiter().GetResult(); + } + catch (Exception e) + { + Log.Error("Exception caught while disposing a reader", e, Connector.Id); + if (e is not PostgresException) + State = ReaderState.Disposed; + } + finally + { + Command.TraceCommandStop(); + } + } + + /// + /// Releases the resources used by the . + /// +#if NETSTANDARD2_0 + public ValueTask DisposeAsync() +#else + public override ValueTask DisposeAsync() +#endif + { + using (NoSynchronizationContextScope.Enter()) + return DisposeAsyncCore(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + async ValueTask DisposeAsyncCore() + { + try + { + await Close(connectionClosing: false, async: true, isDisposing: true); + } + catch (Exception e) + { + Log.Error("Exception caught while disposing a reader", e, Connector.Id); + if (e is not PostgresException) + State = ReaderState.Disposed; + } + finally + { + Command.TraceCommandStop(); + } + } + } + + /// + /// Closes the reader, allowing a new command to be executed. + /// + public override void Close() => Close(connectionClosing: false, async: false, isDisposing: false).GetAwaiter().GetResult(); + + /// + /// Closes the reader, allowing a new command to be executed. + /// +#if NETSTANDARD2_0 + public Task CloseAsync() +#else + public override Task CloseAsync() +#endif + { + using (NoSynchronizationContextScope.Enter()) + return Close(connectionClosing: false, async: true, isDisposing: false); + } + + internal async Task Close(bool connectionClosing, bool async, bool isDisposing) + { + if (State == ReaderState.Closed || State == ReaderState.Disposed) + { + if (isDisposing) + State = ReaderState.Disposed; + return; + } + + // Whenever a connector is broken, it also closes the current reader. + Connector.CurrentReader = null; + + switch (Connector.State) + { + case ConnectorState.Ready: + case ConnectorState.Fetching: + case ConnectorState.Executing: + case ConnectorState.Connecting: + if (State != ReaderState.Consumed) + { + try + { + await Consume(async); + } + catch (Exception ex) when ( + ex is OperationCanceledException || + ex is NpgsqlException && ex.InnerException is TimeoutException) + { + // Timeout/cancellation - completely normal, consume has basically completed. + } + catch (PostgresException) + { + // In the case of a PostgresException, the connection is fine and consume has basically completed. + // Defer throwing the exception until Cleanup is complete. + await Cleanup(async, connectionClosing, isDisposing); + throw; + } + catch + { + Debug.Assert(Connector.IsBroken); + throw; + } + } + break; + case ConnectorState.Closed: + case ConnectorState.Broken: + break; + case ConnectorState.Waiting: + case ConnectorState.Copy: + case ConnectorState.Replication: + Debug.Fail("Bad connector state when closing reader: " + Connector.State); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + await Cleanup(async, connectionClosing, isDisposing); + } + + internal async Task Cleanup(bool async, bool connectionClosing = false, bool isDisposing = false) + { + Log.Trace("Cleaning up reader", Connector.Id); + + // If multiplexing isn't on, _sendTask contains the task for the writing of this command. + // Make sure that this task, which may have executed asynchronously and in parallel with the reading, + // has completed, throwing any exceptions it generated. + // Note: if the following is removed, mysterious concurrent connection usage errors start happening + // on .NET Framework. + if (_sendTask != null) + { + // If the connector is broken, we have no reason to wait for the sendTask to complete + // as we're not going to send anything else over it + // and that can lead to deadlocks (concurrent write and read failure, see #4804) + if (Connector.IsBroken) + { + // Prevent unobserved Task notifications by observing the failed Task exception. + _ = _sendTask.ContinueWith(t => _ = t.Exception, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Current); + } + else + { + try + { + if (async) + await _sendTask; + else + _sendTask.GetAwaiter().GetResult(); + } + catch (Exception e) + { + // TODO: think of a better way to handle exceptions, see #1323 and #3163 + Log.Debug("Exception caught while sending the request", e, Connector.Id); + } + } + } + + State = ReaderState.Closed; + Command.State = CommandState.Idle; + Connector.CurrentReader = null; + if (Log.IsEnabled(NpgsqlLogLevel.Debug)) { + Connector.QueryLogStopWatch.Stop(); + Log.Debug($"Query duration time: {Connector.QueryLogStopWatch.ElapsedMilliseconds}ms", Connector.Id); + Connector.QueryLogStopWatch.Reset(); + } + NpgsqlEventSource.Log.CommandStop(); + Connector.EndUserAction(); + + // The reader shouldn't be unbound, if we're disposing - so the state is set prematurely + if (isDisposing) + State = ReaderState.Disposed; + + if (_connection?.ConnectorBindingScope == ConnectorBindingScope.Reader) + { + // We may unbind the current reader, which also sets the connector to null + var connector = Connector; + UnbindIfNecessary(); + + // TODO: Refactor... Use proper scope + _connection.Connector = null; + connector.Connection = null; + _connection.ConnectorBindingScope = ConnectorBindingScope.None; + + // If the reader is being closed as part of the connection closing, we don't apply + // the reader's CommandBehavior.CloseConnection + if (_behavior.HasFlag(CommandBehavior.CloseConnection) && !connectionClosing) + _connection.Close(); + + connector.ReaderCompleted.SetResult(null); + } + else if (_behavior.HasFlag(CommandBehavior.CloseConnection) && !connectionClosing) + { + Debug.Assert(_connection is not null); + _connection.Close(); + } + + if (ReaderClosed != null) + { + ReaderClosed(this, EventArgs.Empty); + ReaderClosed = null; + } + } + + #endregion + + #region Simple value getters + + /// + /// Gets the value of the specified column as a Boolean. + /// + /// The zero-based column ordinal. + /// The value of the specified column. + public override bool GetBoolean(int ordinal) => GetFieldValue(ordinal); + + /// + /// Gets the value of the specified column as a byte. + /// + /// The zero-based column ordinal. + /// The value of the specified column. + public override byte GetByte(int ordinal) => GetFieldValue(ordinal); + + /// + /// Gets the value of the specified column as a single character. + /// + /// The zero-based column ordinal. + /// The value of the specified column. + public override char GetChar(int ordinal) => GetFieldValue(ordinal); + + /// + /// Gets the value of the specified column as a 16-bit signed integer. + /// + /// The zero-based column ordinal. + /// The value of the specified column. + public override short GetInt16(int ordinal) => GetFieldValue(ordinal); + + /// + /// Gets the value of the specified column as a 32-bit signed integer. + /// + /// The zero-based column ordinal. + /// The value of the specified column. + public override int GetInt32(int ordinal) => GetFieldValue(ordinal); + + /// + /// Gets the value of the specified column as a 64-bit signed integer. + /// + /// The zero-based column ordinal. + /// The value of the specified column. + public override long GetInt64(int ordinal) => GetFieldValue(ordinal); + + /// + /// Gets the value of the specified column as a object. + /// + /// The zero-based column ordinal. + /// The value of the specified column. + public override DateTime GetDateTime(int ordinal) => GetFieldValue(ordinal); + + /// + /// Gets the value of the specified column as an instance of . + /// + /// The zero-based column ordinal. + /// The value of the specified column. + public override string GetString(int ordinal) => GetFieldValue(ordinal); + + /// + /// Gets the value of the specified column as a object. + /// + /// The zero-based column ordinal. + /// The value of the specified column. + public override decimal GetDecimal(int ordinal) => GetFieldValue(ordinal); + + /// + /// Gets the value of the specified column as a double-precision floating point number. + /// + /// The zero-based column ordinal. + /// The value of the specified column. + public override double GetDouble(int ordinal) => GetFieldValue(ordinal); + + /// + /// Gets the value of the specified column as a single-precision floating point number. + /// + /// The zero-based column ordinal. + /// The value of the specified column. + public override float GetFloat(int ordinal) => GetFieldValue(ordinal); + + /// + /// Gets the value of the specified column as a globally-unique identifier (GUID). + /// + /// The zero-based column ordinal. + /// The value of the specified column. + public override Guid GetGuid(int ordinal) => GetFieldValue(ordinal); + + /// + /// Populates an array of objects with the column values of the current row. + /// + /// An array of Object into which to copy the attribute columns. + /// The number of instances of in the array. + public override int GetValues(object[] values) + { + if (values == null) + throw new ArgumentNullException(nameof(values)); + CheckResultSet(); + + var count = Math.Min(FieldCount, values.Length); + for (var i = 0; i < count; i++) + values[i] = GetValue(i); + return count; + } + + /// + /// Gets the value of the specified column as an instance of . + /// + /// The zero-based column ordinal. + /// The value of the specified column. + public override object this[int ordinal] => GetValue(ordinal); + + #endregion + + #region Provider-specific simple type getters + + /// + /// Gets the value of the specified column as an , + /// Npgsql's provider-specific type for dates. + /// + /// + /// PostgreSQL's date type represents dates from 4713 BC to 5874897 AD, while .NET's DateTime + /// only supports years from 1 to 1999. If you require years outside this range use this accessor. + /// The standard method will also return this type, but has + /// the disadvantage of boxing the value. + /// See https://www.postgresql.org/docs/current/static/datatype-datetime.html + /// + /// The zero-based column ordinal. + /// The value of the specified column. + [Obsolete( + "For values outside the range of DateTime/DateOnly, consider using NodaTime (range -9998 to 9999), or read the value as an 'int'. " + + "See https://www.npgsql.org/doc/types/datetime.html for more information.")] + public NpgsqlDate GetDate(int ordinal) => GetFieldValue(ordinal); + + /// + /// Gets the value of the specified column as a TimeSpan, + /// + /// + /// PostgreSQL's interval type has has a resolution of 1 microsecond and ranges from + /// -178000000 to 178000000 years, while .NET's TimeSpan has a resolution of 100 nanoseconds + /// and ranges from roughly -29247 to 29247 years. + /// See https://www.postgresql.org/docs/current/static/datatype-datetime.html + /// + /// The zero-based column ordinal. + /// The value of the specified column. + public TimeSpan GetTimeSpan(int ordinal) => GetFieldValue(ordinal); + + /// + /// Gets the value of the specified column as an , + /// Npgsql's provider-specific type for time spans. + /// + /// + /// PostgreSQL's interval type has has a resolution of 1 microsecond and ranges from + /// -178000000 to 178000000 years, while .NET's TimeSpan has a resolution of 100 nanoseconds + /// and ranges from roughly -29247 to 29247 years. If you require values from outside TimeSpan's + /// range use this accessor. + /// The standard ADO.NET method will also return this + /// type, but has the disadvantage of boxing the value. + /// See https://www.postgresql.org/docs/current/static/datatype-datetime.html + /// + /// The zero-based column ordinal. + /// The value of the specified column. + [Obsolete( + "For values outside the range of TimeSpan, consider using NodaTime (range -9998 to 9999), or read the value as an NpgsqlInterval. " + + "See https://www.npgsql.org/doc/types/datetime.html for more information.")] + public NpgsqlTimeSpan GetInterval(int ordinal) => GetFieldValue(ordinal); + + /// + /// Gets the value of the specified column as an , + /// Npgsql's provider-specific type for date/time timestamps. Note that this type covers + /// both PostgreSQL's "timestamp with time zone" and "timestamp without time zone" types, + /// which differ only in how they are converted upon input/output. + /// + /// + /// PostgreSQL's timestamp type represents dates from 4713 BC to 5874897 AD, while .NET's DateTime + /// only supports years from 1 to 1999. If you require years outside this range use this accessor. + /// The standard method will also return this type, but has + /// the disadvantage of boxing the value. + /// See https://www.postgresql.org/docs/current/static/datatype-datetime.html + /// + /// The zero-based column ordinal. + /// The value of the specified column. + [Obsolete( + "For values outside the range of DateTime, consider using NodaTime (range -9998 to 9999), or read the value as a 'long'. " + + "See https://www.npgsql.org/doc/types/datetime.html for more information.")] + public NpgsqlDateTime GetTimeStamp(int ordinal) => GetFieldValue(ordinal); + + /// + protected override DbDataReader GetDbDataReader(int ordinal) => GetData(ordinal); + + /// + /// Returns a nested data reader for the requested column. + /// The column type must be a record or a to Npgsql known composite type, or an array thereof. + /// Currently only supported in non-sequential mode. + /// + /// The zero-based column ordinal. + /// A data reader. + public new NpgsqlNestedDataReader GetData(int ordinal) + { + var field = CheckRowAndGetField(ordinal); + var type = field.PostgresType; + var isArray = type is PostgresArrayType; + var elementType = isArray ? ((PostgresArrayType)type).Element : type; + var compositeType = elementType as PostgresCompositeType; + if (elementType.InternalName != "record" && compositeType == null) + throw new InvalidCastException("GetData() not supported for type " + field.TypeDisplayName); + + SeekToColumn(ordinal, false).GetAwaiter().GetResult(); + if (ColumnLen == -1) + ThrowHelper.ThrowInvalidCastException_NoValue(field); + + if (_isSequential) + throw new NotSupportedException("GetData() not supported in sequential mode."); + + var reader = CachedFreeNestedDataReader; + if (reader != null) + { + CachedFreeNestedDataReader = null; + reader.Init(UniqueRowId, compositeType); + } + else + { + reader = new NpgsqlNestedDataReader(this, null, UniqueRowId, 1, compositeType); + } + if (isArray) + reader.InitArray(); + else + reader.InitSingleRow(); + return reader; + } + + #endregion + + #region Special binary getters + + /// + /// Reads a stream of bytes from the specified column, starting at location indicated by dataOffset, into the buffer, starting at the location indicated by bufferOffset. + /// + /// The zero-based column ordinal. + /// The index within the row from which to begin the read operation. + /// The buffer into which to copy the data. + /// The index with the buffer to which the data will be copied. + /// The maximum number of characters to read. + /// The actual number of bytes read. + public override long GetBytes(int ordinal, long dataOffset, byte[]? buffer, int bufferOffset, int length) + { + if (dataOffset < 0 || dataOffset > int.MaxValue) + throw new ArgumentOutOfRangeException(nameof(dataOffset), dataOffset, $"dataOffset must be between {0} and {int.MaxValue}"); + if (buffer != null && (bufferOffset < 0 || bufferOffset >= buffer.Length + 1)) + throw new IndexOutOfRangeException($"bufferOffset must be between {0} and {(buffer.Length)}"); + if (buffer != null && (length < 0 || length > buffer.Length - bufferOffset)) + throw new IndexOutOfRangeException($"length must be between {0} and {buffer.Length - bufferOffset}"); + + var field = CheckRowAndGetField(ordinal); + var handler = field.Handler; + if (!(handler is ByteaHandler)) + throw new InvalidCastException("GetBytes() not supported for type " + field.Name); + + SeekToColumn(ordinal, false).GetAwaiter().GetResult(); + if (ColumnLen == -1) + ThrowHelper.ThrowInvalidCastException_NoValue(field); + + if (buffer == null) + return ColumnLen; + + var dataOffset2 = (int)dataOffset; + SeekInColumn(dataOffset2, false).GetAwaiter().GetResult(); + + // Attempt to read beyond the end of the column + if (dataOffset2 + length > ColumnLen) + length = Math.Max(ColumnLen - dataOffset2, 0); + + var left = length; + while (left > 0) + { + var read = Buffer.Read(new Span(buffer, bufferOffset, left)); + bufferOffset += read; + left -= read; + } + + PosInColumn += length; + + return length; + } + + /// + /// Retrieves data as a . + /// + /// The zero-based column ordinal. + /// The returned object. + public override Stream GetStream(int ordinal) => GetStream(ordinal, false).Result; + + /// + /// Retrieves data as a . + /// + /// The zero-based column ordinal. + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// The returned object. + public Task GetStreamAsync(int ordinal, CancellationToken cancellationToken = default) + { + using (NoSynchronizationContextScope.Enter()) + return GetStream(ordinal, true, cancellationToken).AsTask(); + } + + ValueTask GetStream(int ordinal, bool async, CancellationToken cancellationToken = default) => + GetStreamInternal(CheckRowAndGetField(ordinal), ordinal, async, cancellationToken); + + ValueTask GetStreamInternal(FieldDescription field, int ordinal, bool async, CancellationToken cancellationToken = default) + { + if (_columnStream is { IsDisposed: false }) + throw new InvalidOperationException("A stream is already open for this reader"); + + var t = SeekToColumn(ordinal, async, cancellationToken); + if (!t.IsCompleted) + return new ValueTask(GetStreamLong(this, field, t, cancellationToken)); + + if (ColumnLen == -1) + ThrowHelper.ThrowInvalidCastException_NoValue(field); + + PosInColumn += ColumnLen; + return new ValueTask(_columnStream = (NpgsqlReadBuffer.ColumnStream)Buffer.GetStream(ColumnLen, !_isSequential)); + + static async Task GetStreamLong(NpgsqlDataReader reader, FieldDescription field, Task seekTask, CancellationToken cancellationToken) + { + using var registration = reader.Connector.StartNestedCancellableOperation(cancellationToken, attemptPgCancellation: false); + + await seekTask; + if (reader.ColumnLen == -1) + ThrowHelper.ThrowInvalidCastException_NoValue(field); + + reader.PosInColumn += reader.ColumnLen; + return reader._columnStream = (NpgsqlReadBuffer.ColumnStream)reader.Buffer.GetStream(reader.ColumnLen, !reader._isSequential); + } + } + + #endregion + + #region Special text getters + + /// + /// Reads a stream of characters from the specified column, starting at location indicated by dataOffset, into the buffer, starting at the location indicated by bufferOffset. + /// + /// The zero-based column ordinal. + /// The index within the row from which to begin the read operation. + /// The buffer into which to copy the data. + /// The index with the buffer to which the data will be copied. + /// The maximum number of characters to read. + /// The actual number of characters read. + public override long GetChars(int ordinal, long dataOffset, char[]? buffer, int bufferOffset, int length) + { + if (dataOffset < 0 || dataOffset > int.MaxValue) + throw new ArgumentOutOfRangeException(nameof(dataOffset), dataOffset, $"dataOffset must be between {0} and {int.MaxValue}"); + if (buffer != null && (bufferOffset < 0 || bufferOffset >= buffer.Length + 1)) + throw new IndexOutOfRangeException($"bufferOffset must be between {0} and {(buffer.Length)}"); + if (buffer != null && (length < 0 || length > buffer.Length - bufferOffset)) + throw new IndexOutOfRangeException($"length must be between {0} and {buffer.Length - bufferOffset}"); + + var field = CheckRowAndGetField(ordinal); + var handler = field.Handler as TextHandler; + if (handler == null) + throw new InvalidCastException("The GetChars method is not supported for type " + field.Name); + + SeekToColumn(ordinal, false).GetAwaiter().GetResult(); + if (ColumnLen == -1) + ThrowHelper.ThrowInvalidCastException_NoValue(field); + + if (PosInColumn == 0) + _charPos = 0; + + var decoder = Buffer.TextEncoding.GetDecoder(); + + if (buffer == null) + { + // Note: Getting the length of a text column means decoding the entire field, + // very inefficient and also consumes the column in sequential mode. But this seems to + // be SqlClient's behavior as well. + var (bytesSkipped, charsSkipped) = SkipChars(decoder, int.MaxValue, ColumnLen - PosInColumn); + Debug.Assert(bytesSkipped == ColumnLen - PosInColumn); + PosInColumn += bytesSkipped; + _charPos += charsSkipped; + return _charPos; + } + + if (PosInColumn == ColumnLen || dataOffset < _charPos) + { + // Either the column has already been read (e.g. GetString()) or a previous GetChars() + // has positioned us in the column *after* the requested read start offset. Seek back + // (this will throw for sequential) + SeekInColumn(0, false).GetAwaiter().GetResult(); + _charPos = 0; + } + + if (dataOffset > _charPos) + { + var charsToSkip = (int)dataOffset - _charPos; + var (bytesSkipped, charsSkipped) = SkipChars(decoder, charsToSkip, ColumnLen - PosInColumn); + decoder.Reset(); + PosInColumn += bytesSkipped; + _charPos += charsSkipped; + if (charsSkipped < charsToSkip) // data offset is beyond the column's end + return 0; + } + + // We're now positioned at the start of the segment of characters we need to read. + if (length == 0) + return 0; + + var (bytesRead, charsRead) = DecodeChars(decoder, buffer, bufferOffset, length, ColumnLen - PosInColumn); + + PosInColumn += bytesRead; + _charPos += charsRead; + return charsRead; + } + + (int BytesRead, int CharsRead) DecodeChars(Decoder decoder, char[] output, int outputOffset, int charCount, int byteCount) + { + var (bytesRead, charsRead) = (0, 0); + + while (true) + { + Buffer.Ensure(1); // Make sure we have at least some data + + var maxBytes = Math.Min(byteCount - bytesRead, Buffer.ReadBytesLeft); + decoder.Convert(Buffer.Buffer, Buffer.ReadPosition, maxBytes, output, outputOffset, charCount - charsRead, false, + out var bytesUsed, out var charsUsed, out _); + Buffer.ReadPosition += bytesUsed; + bytesRead += bytesUsed; + charsRead += charsUsed; + if (charsRead == charCount || bytesRead == byteCount) + break; + outputOffset += charsUsed; + Buffer.Clear(); + } + + return (bytesRead, charsRead); + } + + internal (int BytesSkipped, int CharsSkipped) SkipChars(Decoder decoder, int charCount, int byteCount) + { + // TODO: Allocate on the stack with Span + if (_tempCharBuf == null) + _tempCharBuf = new char[1024]; + var (charsSkipped, bytesSkipped) = (0, 0); + while (charsSkipped < charCount && bytesSkipped < byteCount) + { + var (bytesRead, charsRead) = DecodeChars(decoder, _tempCharBuf, 0, Math.Min(charCount, _tempCharBuf.Length), byteCount); + bytesSkipped += bytesRead; + charsSkipped += charsRead; + } + return (bytesSkipped, charsSkipped); + } + + /// + /// Retrieves data as a . + /// + /// The zero-based column ordinal. + /// The returned object. + public override TextReader GetTextReader(int ordinal) + => GetTextReader(ordinal, false).Result; + + /// + /// Retrieves data as a . + /// + /// The zero-based column ordinal. + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// The returned object. + public Task GetTextReaderAsync(int ordinal, CancellationToken cancellationToken = default) + { + using (NoSynchronizationContextScope.Enter()) + return GetTextReader(ordinal, true, cancellationToken).AsTask(); + } + + async ValueTask GetTextReader(int ordinal, bool async, CancellationToken cancellationToken = default) + { + var field = CheckRowAndGetField(ordinal); + if (field.Handler is ITextReaderHandler handler) + return handler.GetTextReader(async + ? await GetStreamInternal(field, ordinal, true, cancellationToken) + : GetStreamInternal(field, ordinal, false, CancellationToken.None).Result); + + throw new InvalidCastException($"The GetTextReader method is not supported for type {field.Handler.PgDisplayName}"); + } + + #endregion + + #region GetFieldValue + + /// + /// Asynchronously gets the value of the specified column as a type. + /// + /// The type of the value to be returned. + /// The type of the value to be returned. + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// + public override Task GetFieldValueAsync(int ordinal, CancellationToken cancellationToken) + { + if (typeof(T) == typeof(Stream)) + return (Task)(object)GetStreamAsync(ordinal, cancellationToken); + + if (typeof(T) == typeof(TextReader)) + return (Task)(object)GetTextReaderAsync(ordinal, cancellationToken); + + // In non-sequential, we know that the column is already buffered - no I/O will take place + if (!_isSequential) + return Task.FromResult(GetFieldValue(ordinal)); + + using (NoSynchronizationContextScope.Enter()) + return GetFieldValueSequential(ordinal, true, cancellationToken).AsTask(); + } + + /// + /// Synchronously gets the value of the specified column as a type. + /// + /// Synchronously gets the value of the specified column as a type. + /// The column to be retrieved. + /// The column to be retrieved. + public override T GetFieldValue(int ordinal) + { + if (typeof(T) == typeof(Stream)) + return (T)(object)GetStream(ordinal); + + if (typeof(T) == typeof(TextReader)) + return (T)(object)GetTextReader(ordinal); + + if (_isSequential) + return GetFieldValueSequential(ordinal, false).GetAwaiter().GetResult(); + + // In non-sequential, we know that the column is already buffered - no I/O will take place + + var field = CheckRowAndGetField(ordinal); + SeekToColumnNonSequential(ordinal); + + if (ColumnLen == -1) + { + // When T is a Nullable (and only in that case), we support returning null + if (NullableHandler.Exists) + return default!; + + if (typeof(T) == typeof(object)) + return (T)(object)DBNull.Value; + + ThrowHelper.ThrowInvalidCastException_NoValue(field); + } + + var position = Buffer.ReadPosition; + try + { + return NullableHandler.Exists + ? NullableHandler.Read(field.Handler, Buffer, ColumnLen, field) + : typeof(T) == typeof(object) + ? (T)field.Handler.ReadAsObject(Buffer, ColumnLen, field) + : field.Handler.Read(Buffer, ColumnLen, field); + } + catch + { + if (Connector.State != ConnectorState.Broken) + { + var writtenBytes = Buffer.ReadPosition - position; + var remainingBytes = ColumnLen - writtenBytes; + if (remainingBytes > 0) + Buffer.Skip(remainingBytes, false).GetAwaiter().GetResult(); + } + throw; + } + finally + { + // Important: position must still be updated + PosInColumn += ColumnLen; + } + } + + async ValueTask GetFieldValueSequential(int column, bool async, CancellationToken cancellationToken = default) + { + using var registration = Connector.StartNestedCancellableOperation(cancellationToken, attemptPgCancellation: false); + + var field = CheckRowAndGetField(column); + await SeekToColumnSequential(column, async, CancellationToken.None); + CheckColumnStart(); + + if (ColumnLen == -1) + { + // When T is a Nullable (and only in that case), we support returning null + if (NullableHandler.Exists) + return default!; + + if (typeof(T) == typeof(object)) + return (T)(object)DBNull.Value; + + ThrowHelper.ThrowInvalidCastException_NoValue(field); + } + + var position = Buffer.ReadPosition; + try + { + return NullableHandler.Exists + ? ColumnLen <= Buffer.ReadBytesLeft + ? NullableHandler.Read(field.Handler, Buffer, ColumnLen, field) + : await NullableHandler.ReadAsync(field.Handler, Buffer, ColumnLen, async, field) + : typeof(T) == typeof(object) + ? ColumnLen <= Buffer.ReadBytesLeft + ? (T)field.Handler.ReadAsObject(Buffer, ColumnLen, field) + : (T)await field.Handler.ReadAsObject(Buffer, ColumnLen, async, field) + : ColumnLen <= Buffer.ReadBytesLeft + ? field.Handler.Read(Buffer, ColumnLen, field) + : await field.Handler.Read(Buffer, ColumnLen, async, field); + } + catch + { + if (Connector.State != ConnectorState.Broken) + { + var writtenBytes = Buffer.ReadPosition - position; + var remainingBytes = ColumnLen - writtenBytes; + if (remainingBytes > 0) + await Buffer.Skip(remainingBytes, async); + } + throw; + } + finally + { + // Important: position must still be updated + PosInColumn += ColumnLen; + } + } + + #endregion + + #region GetValue + + /// + /// Gets the value of the specified column as an instance of . + /// + /// The zero-based column ordinal. + /// The value of the specified column. + public override object GetValue(int ordinal) + { + var fieldDescription = CheckRowAndGetField(ordinal); + + if (_isSequential) { + SeekToColumnSequential(ordinal, false).GetAwaiter().GetResult(); + CheckColumnStart(); + } else + SeekToColumnNonSequential(ordinal); + + if (ColumnLen == -1) + return DBNull.Value; + + object result; + var position = Buffer.ReadPosition; + try + { + result = _isSequential + ? fieldDescription.Handler.ReadAsObject(Buffer, ColumnLen, false, fieldDescription).GetAwaiter().GetResult() + : fieldDescription.Handler.ReadAsObject(Buffer, ColumnLen, fieldDescription); + } + catch + { + if (Connector.State != ConnectorState.Broken) + { + var writtenBytes = Buffer.ReadPosition - position; + var remainingBytes = ColumnLen - writtenBytes; + if (remainingBytes > 0) + Buffer.Skip(remainingBytes, false).GetAwaiter().GetResult(); + } + throw; + } + finally + { + // Important: position must still be updated + PosInColumn += ColumnLen; + } + + // Used for Entity Framework <= 6 compability + var objectResultType = Command.ObjectResultTypes?[ordinal]; + if (objectResultType != null) + { + result = objectResultType == typeof(DateTimeOffset) + ? new DateTimeOffset((DateTime)result) + : Convert.ChangeType(result, objectResultType)!; + } + + return result; + } + + /// + /// Gets the value of the specified column as an instance of . + /// + /// The zero-based column ordinal. + /// The value of the specified column. + public override object GetProviderSpecificValue(int ordinal) + { + var fieldDescription = CheckRowAndGetField(ordinal); + + if (_isSequential) + { + SeekToColumnSequential(ordinal, false).GetAwaiter().GetResult(); + CheckColumnStart(); + } + else + SeekToColumnNonSequential(ordinal); + + if (ColumnLen == -1) + return DBNull.Value; + + var position = Buffer.ReadPosition; + try + { + return _isSequential + ? fieldDescription.Handler.ReadPsvAsObject(Buffer, ColumnLen, false, fieldDescription).GetAwaiter().GetResult() + : fieldDescription.Handler.ReadPsvAsObject(Buffer, ColumnLen, fieldDescription); + } + catch + { + if (Connector.State != ConnectorState.Broken) + { + var writtenBytes = Buffer.ReadPosition - position; + var remainingBytes = ColumnLen - writtenBytes; + if (remainingBytes > 0) + Buffer.Skip(remainingBytes, false).GetAwaiter().GetResult(); + } + throw; + } + finally + { + // Important: position must still be updated + PosInColumn += ColumnLen; + } + } + + /// + /// Gets the value of the specified column as an instance of . + /// + /// The name of the column. + /// The value of the specified column. + public override object this[string name] => GetValue(GetOrdinal(name)); + + #endregion + + #region IsDBNull + + /// + /// Gets a value that indicates whether the column contains nonexistent or missing values. + /// + /// The zero-based column ordinal. + /// true if the specified column is equivalent to ; otherwise false. + public override bool IsDBNull(int ordinal) + { + CheckRowAndGetField(ordinal); + + if (_isSequential) + SeekToColumnSequential(ordinal, false).GetAwaiter().GetResult(); + else + SeekToColumnNonSequential(ordinal); + + return ColumnLen == -1; + } + + /// + /// An asynchronous version of , which gets a value that indicates whether the column contains non-existent or missing values. + /// The parameter is currently ignored. + /// + /// The zero-based column to be retrieved. + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// true if the specified column value is equivalent to otherwise false. + public override Task IsDBNullAsync(int ordinal, CancellationToken cancellationToken) + { + CheckRowAndGetField(ordinal); + + if (!_isSequential) + return IsDBNull(ordinal) ? PGUtil.TrueTask : PGUtil.FalseTask; + + using (NoSynchronizationContextScope.Enter()) + return IsDBNullAsyncInternal(ordinal, cancellationToken); + + // ReSharper disable once InconsistentNaming + async Task IsDBNullAsyncInternal(int ordinal, CancellationToken cancellationToken) + { + using var registration = Connector.StartNestedCancellableOperation(cancellationToken, attemptPgCancellation: false); + + await SeekToColumn(ordinal, true, cancellationToken); + return ColumnLen == -1; + } + } + + #endregion + + #region Other public accessors + + /// + /// Gets the column ordinal given the name of the column. + /// + /// The name of the column. + /// The zero-based column ordinal. + public override int GetOrdinal(string name) + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentException("name cannot be empty", nameof(name)); + CheckClosedOrDisposed(); + if (RowDescription is null) + throw new InvalidOperationException("No resultset is currently being traversed"); + return RowDescription.GetFieldIndex(name); + } + + /// + /// Gets a representation of the PostgreSQL data type for the specified field. + /// The returned representation can be used to access various information about the field. + /// + /// The zero-based column index. + public PostgresType GetPostgresType(int ordinal) => GetField(ordinal).PostgresType; + + /// + /// Gets the data type information for the specified field. + /// This is be the PostgreSQL type name (e.g. double precision), not the .NET type + /// (see for that). + /// + /// The zero-based column index. + public override string GetDataTypeName(int ordinal) => GetField(ordinal).TypeDisplayName; + + /// + /// Gets the OID for the PostgreSQL type for the specified field, as it appears in the pg_type table. + /// + /// + /// This is a PostgreSQL-internal value that should not be relied upon and should only be used for + /// debugging purposes. + /// + /// The zero-based column index. + public uint GetDataTypeOID(int ordinal) => GetField(ordinal).TypeOID; + + /// + /// Gets the data type of the specified column. + /// + /// The zero-based column ordinal. + /// The data type of the specified column. + public override Type GetFieldType(int ordinal) + => Command.ObjectResultTypes?[ordinal] + ?? GetField(ordinal).FieldType; + + /// + /// Returns the provider-specific field type of the specified column. + /// + /// The zero-based column ordinal. + /// The Type object that describes the data type of the specified column. + public override Type GetProviderSpecificFieldType(int ordinal) + { + var fieldDescription = GetField(ordinal); + return fieldDescription.Handler.GetProviderSpecificFieldType(fieldDescription); + } + + /// + /// Gets all provider-specific attribute columns in the collection for the current row. + /// + /// An array of Object into which to copy the attribute columns. + /// The number of instances of in the array. + public override int GetProviderSpecificValues(object[] values) + { + if (values == null) + throw new ArgumentNullException(nameof(values)); + if (State != ReaderState.InResult) + { + throw State == ReaderState.Disposed + ? new ObjectDisposedException(nameof(NpgsqlDataReader)) + : new InvalidOperationException("No row is available"); + } + + var count = Math.Min(FieldCount, values.Length); + for (var i = 0; i < count; i++) + values[i] = GetProviderSpecificValue(i); + return count; + } + + /// + /// Returns an that can be used to iterate through the rows in the data reader. + /// + /// An that can be used to iterate through the rows in the data reader. + public override IEnumerator GetEnumerator() + => new DbEnumerator(this); + + /// + /// Returns schema information for the columns in the current resultset. + /// + /// + public ReadOnlyCollection GetColumnSchema() + => GetColumnSchema(async: false).GetAwaiter().GetResult(); + + ReadOnlyCollection IDbColumnSchemaGenerator.GetColumnSchema() + => new(GetColumnSchema().Select(c => (DbColumn)c).ToList()); + + /// + /// Asynchronously returns schema information for the columns in the current resultset. + /// + /// +#if NET5_0_OR_GREATER + public new Task> GetColumnSchemaAsync(CancellationToken cancellationToken = default) +#else + public Task> GetColumnSchemaAsync(CancellationToken cancellationToken = default) +#endif + { + using (NoSynchronizationContextScope.Enter()) + return GetColumnSchema(async: true, cancellationToken); + } + + Task> GetColumnSchema(bool async, CancellationToken cancellationToken = default) + => RowDescription == null || RowDescription.Count == 0 + ? Task.FromResult(new List().AsReadOnly()) + : new DbColumnSchemaGenerator(_connection!, RowDescription, _behavior.HasFlag(CommandBehavior.KeyInfo)) + .GetColumnSchema(async, cancellationToken); + + #endregion + + #region Schema metadata table + + /// + /// Returns a System.Data.DataTable that describes the column metadata of the DataReader. + /// + [UnconditionalSuppressMessage( + "Composite type mapping currently isn't trimming-safe, and warnings are generated at the MapComposite level.", "IL2026")] + public override DataTable? GetSchemaTable() + => GetSchemaTable(async: false).GetAwaiter().GetResult(); + + /// + /// Asynchronously returns a System.Data.DataTable that describes the column metadata of the DataReader. + /// + [UnconditionalSuppressMessage( + "Composite type mapping currently isn't trimming-safe, and warnings are generated at the MapComposite level.", "IL2026")] +#if NET5_0_OR_GREATER + public override Task GetSchemaTableAsync(CancellationToken cancellationToken = default) +#else + public Task GetSchemaTableAsync(CancellationToken cancellationToken = default) +#endif + { + using (NoSynchronizationContextScope.Enter()) + return GetSchemaTable(async: true, cancellationToken); + } + + [UnconditionalSuppressMessage( + "Composite type mapping currently isn't trimming-safe, and warnings are generated at the MapComposite level.", "IL2026")] + async Task GetSchemaTable(bool async, CancellationToken cancellationToken = default) + { + if (FieldCount == 0) // No resultset + return null; + + var table = new DataTable("SchemaTable"); + + // Note: column order is important to match SqlClient's, some ADO.NET users appear + // to assume ordering (see #1671) + table.Columns.Add("ColumnName", typeof(string)); + table.Columns.Add("ColumnOrdinal", typeof(int)); + table.Columns.Add("ColumnSize", typeof(int)); + table.Columns.Add("NumericPrecision", typeof(int)); + table.Columns.Add("NumericScale", typeof(int)); + table.Columns.Add("IsUnique", typeof(bool)); + table.Columns.Add("IsKey", typeof(bool)); + table.Columns.Add("BaseServerName", typeof(string)); + table.Columns.Add("BaseCatalogName", typeof(string)); + table.Columns.Add("BaseColumnName", typeof(string)); + table.Columns.Add("BaseSchemaName", typeof(string)); + table.Columns.Add("BaseTableName", typeof(string)); + table.Columns.Add("DataType", typeof(Type)); + table.Columns.Add("AllowDBNull", typeof(bool)); + table.Columns.Add("ProviderType", typeof(int)); + table.Columns.Add("IsAliased", typeof(bool)); + table.Columns.Add("IsExpression", typeof(bool)); + table.Columns.Add("IsIdentity", typeof(bool)); + table.Columns.Add("IsAutoIncrement", typeof(bool)); + table.Columns.Add("IsRowVersion", typeof(bool)); + table.Columns.Add("IsHidden", typeof(bool)); + table.Columns.Add("IsLong", typeof(bool)); + table.Columns.Add("IsReadOnly", typeof(bool)); + table.Columns.Add("ProviderSpecificDataType", typeof(Type)); + table.Columns.Add("DataTypeName", typeof(string)); + + foreach (var column in await GetColumnSchema(async, cancellationToken)) + { + var row = table.NewRow(); + + row["ColumnName"] = column.ColumnName; + row["ColumnOrdinal"] = column.ColumnOrdinal ?? -1; + row["ColumnSize"] = column.ColumnSize ?? -1; + row["NumericPrecision"] = column.NumericPrecision ?? 0; + row["NumericScale"] = column.NumericScale ?? 0; + row["IsUnique"] = column.IsUnique == true; + row["IsKey"] = column.IsKey == true; + row["BaseServerName"] = ""; + row["BaseCatalogName"] = column.BaseCatalogName; + row["BaseColumnName"] = column.BaseColumnName; + row["BaseSchemaName"] = column.BaseSchemaName; + row["BaseTableName"] = column.BaseTableName; + row["DataType"] = column.DataType; + row["AllowDBNull"] = (object?)column.AllowDBNull ?? DBNull.Value; + row["ProviderType"] = column.NpgsqlDbType ?? NpgsqlDbType.Unknown; + row["IsAliased"] = column.IsAliased == true; + row["IsExpression"] = column.IsExpression == true; + row["IsIdentity"] = column.IsIdentity == true; + row["IsAutoIncrement"] = column.IsAutoIncrement == true; + row["IsRowVersion"] = false; + row["IsHidden"] = column.IsHidden == true; + row["IsLong"] = column.IsLong == true; + row["DataTypeName"] = column.DataTypeName; + + table.Rows.Add(row); + } + + return table; + } + + #endregion Schema metadata table + + #region Seeking + + Task SeekToColumn(int column, bool async, CancellationToken cancellationToken = default) + { + if (_isSequential) + return SeekToColumnSequential(column, async, cancellationToken); + SeekToColumnNonSequential(column); + return Task.CompletedTask; + } + + void SeekToColumnNonSequential(int column) + { + // Shut down any streaming going on on the column + if (_columnStream != null) + { + _columnStream.Dispose(); + _columnStream = null; + } + + for (var lastColumnRead = _columns.Count; column >= lastColumnRead; lastColumnRead++) + { + int lastColumnLen; + (Buffer.ReadPosition, lastColumnLen) = _columns[lastColumnRead-1]; + if (lastColumnLen != -1) + Buffer.ReadPosition += lastColumnLen; + var len = Buffer.ReadInt32(); + _columns.Add((Buffer.ReadPosition, len)); + } + + (Buffer.ReadPosition, ColumnLen) = _columns[column]; + _column = column; + PosInColumn = 0; + } + + /// + /// Seeks to the given column. The 4-byte length is read and stored in . + /// + async Task SeekToColumnSequential(int column, bool async, CancellationToken cancellationToken = default) + { + if (column < 0 || column >= _numColumns) + throw new IndexOutOfRangeException("Column index out of range"); + + if (column < _column) + throw new InvalidOperationException($"Invalid attempt to read from column ordinal '{column}'. With CommandBehavior.SequentialAccess, you may only read from column ordinal '{_column}' or greater."); + + if (column == _column) + return; + + // Need to seek forward + + // Shut down any streaming going on on the column + if (_columnStream != null) + { + _columnStream.Dispose(); + _columnStream = null; + // Disposing the stream leaves us at the end of the column + PosInColumn = ColumnLen; + } + + // Skip to end of column if needed + // TODO: Simplify by better initializing _columnLen/_posInColumn + var remainingInColumn = ColumnLen == -1 ? 0 : ColumnLen - PosInColumn; + if (remainingInColumn > 0) + await Buffer.Skip(remainingInColumn, async); + + // Skip over unwanted fields + for (; _column < column - 1; _column++) + { + await Buffer.Ensure(4, async); + var len = Buffer.ReadInt32(); + if (len != -1) + await Buffer.Skip(len, async); + } + + await Buffer.Ensure(4, async); + ColumnLen = Buffer.ReadInt32(); + PosInColumn = 0; + _column = column; + } + + Task SeekInColumn(int posInColumn, bool async, CancellationToken cancellationToken = default) + { + if (_isSequential) + return SeekInColumnSequential(posInColumn, async); + + if (posInColumn > ColumnLen) + posInColumn = ColumnLen; + + Buffer.ReadPosition = _columns[_column].Offset + posInColumn; + PosInColumn = posInColumn; + return Task.CompletedTask; + + async Task SeekInColumnSequential(int posInColumn, bool async) + { + Debug.Assert(_column > -1); + + if (posInColumn < PosInColumn) + throw new InvalidOperationException("Attempt to read a position in the column which has already been read"); + + if (posInColumn > ColumnLen) + posInColumn = ColumnLen; + + if (posInColumn > PosInColumn) + { + await Buffer.Skip(posInColumn - PosInColumn, async); + PosInColumn = posInColumn; + } + } + } + + #endregion + + #region ConsumeRow + + Task ConsumeRow(bool async) + { + Debug.Assert(State == ReaderState.InResult || State == ReaderState.BeforeResult); + + UniqueRowId++; + + if (!_canConsumeRowNonSequentially) + return ConsumeRowSequential(async); + + // We get here, if we're in a non-sequential mode (or the row is already in the buffer) + ConsumeRowNonSequential(); + return Task.CompletedTask; + + async Task ConsumeRowSequential(bool async) + { + if (_columnStream != null) + { + _columnStream.Dispose(); + _columnStream = null; + // Disposing the stream leaves us at the end of the column + PosInColumn = ColumnLen; + } + + // TODO: Potential for code-sharing with ReadColumn above, which also skips + // Skip to end of column if needed + var remainingInColumn = ColumnLen == -1 ? 0 : ColumnLen - PosInColumn; + if (remainingInColumn > 0) + await Buffer.Skip(remainingInColumn, async); + + // Skip over the remaining columns in the row + for (; _column < _numColumns - 1; _column++) + { + await Buffer.Ensure(4, async); + var len = Buffer.ReadInt32(); + if (len != -1) + await Buffer.Skip(len, async); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void ConsumeRowNonSequential() + { + Debug.Assert(State == ReaderState.InResult || State == ReaderState.BeforeResult); + + if (_columnStream != null) + { + _columnStream.Dispose(); + _columnStream = null; + // Disposing the stream leaves us at the end of the column + PosInColumn = ColumnLen; + } + Buffer.ReadPosition = _dataMsgEnd; + } + + #endregion + + #region Checks + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void CheckResultSet() + { + switch (State) + { + case ReaderState.BeforeResult: + case ReaderState.InResult: + break; + case ReaderState.Closed: + throw new InvalidOperationException("The reader is closed"); + case ReaderState.Disposed: + throw new ObjectDisposedException(nameof(NpgsqlDataReader)); + default: + throw new InvalidOperationException("No resultset is currently being traversed"); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + FieldDescription CheckRowAndGetField(int column) + { + switch (State) + { + case ReaderState.InResult: + break; + case ReaderState.Closed: + throw new InvalidOperationException("The reader is closed"); + case ReaderState.Disposed: + throw new ObjectDisposedException(nameof(NpgsqlDataReader)); + default: + throw new InvalidOperationException("No row is available"); + } + + if (column < 0 || column >= RowDescription!.Count) + throw new IndexOutOfRangeException($"Column must be between {0} and {RowDescription!.Count - 1}"); + + return RowDescription[column]; + } + + /// + /// Checks that we have a RowDescription, but not necessary an actual resultset + /// (for operations which work in SchemaOnly mode. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + FieldDescription GetField(int column) + { + if (RowDescription == null) + throw new InvalidOperationException("No resultset is currently being traversed"); + + if (column < 0 || column >= RowDescription.Count) + throw new IndexOutOfRangeException($"Column must be between {0} and {RowDescription.Count - 1}"); + + return RowDescription[column]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void CheckColumnStart() + { + Debug.Assert(_isSequential); + if (PosInColumn != 0) + throw new InvalidOperationException("Attempt to read a position in the column which has already been read"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void CheckClosedOrDisposed() + { + switch (State) + { + case ReaderState.Closed: + throw new InvalidOperationException("The reader is closed"); + case ReaderState.Disposed: + throw new ObjectDisposedException(nameof(NpgsqlDataReader)); + } + } + + #endregion + + #region Misc + + /// + /// Unbinds reader from the connector. + /// Should be called before the connector is returned to the pool. + /// + internal void UnbindIfNecessary() + { + // We're closing the connection, but reader is not yet disposed + // We have to unbind the reader from the connector, otherwise there could be a concurency issues + // See #3126 and #3290 + if (State != ReaderState.Disposed) + { + Connector.DataReader = Connector.UnboundDataReader is { State: ReaderState.Disposed } previousReader + ? previousReader + : new NpgsqlDataReader(Connector); + Connector.UnboundDataReader = this; + } + } + + #endregion +} + +enum ReaderState +{ + BeforeResult, + InResult, + BetweenResults, + Consumed, + Closed, + Disposed, +} diff --git a/LibExternal/Npgsql/NpgsqlDatabaseInfoCacheKey.cs b/LibExternal/Npgsql/NpgsqlDatabaseInfoCacheKey.cs new file mode 100644 index 0000000..969c1f9 --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlDatabaseInfoCacheKey.cs @@ -0,0 +1,31 @@ +using System; + +namespace Npgsql; + +readonly struct NpgsqlDatabaseInfoCacheKey : IEquatable +{ + public readonly int Port; + public readonly string? Host; + public readonly string? Database; + public readonly ServerCompatibilityMode CompatibilityMode; + + public NpgsqlDatabaseInfoCacheKey(NpgsqlConnectionStringBuilder connectionString) + { + Port = connectionString.Port; + Host = connectionString.Host; + Database = connectionString.Database; + CompatibilityMode = connectionString.ServerCompatibilityMode; + } + + public bool Equals(NpgsqlDatabaseInfoCacheKey other) => + Port == other.Port && + Host == other.Host && + Database == other.Database && + CompatibilityMode == other.CompatibilityMode; + + public override bool Equals(object? obj) => + obj is NpgsqlDatabaseInfoCacheKey key && key.Equals(this); + + public override int GetHashCode() => + HashCode.Combine(Port, Host, Database, CompatibilityMode); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlEventSource.cs b/LibExternal/Npgsql/NpgsqlEventSource.cs new file mode 100644 index 0000000..e708a04 --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlEventSource.cs @@ -0,0 +1,237 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using System.Diagnostics.Tracing; +using System.Runtime.CompilerServices; + +namespace Npgsql; + +sealed class NpgsqlEventSource : EventSource +{ + public static readonly NpgsqlEventSource Log = new(); + + const string EventSourceName = "Npgsql"; + + internal const int CommandStartId = 3; + internal const int CommandStopId = 4; + +#if !NETSTANDARD2_0 + IncrementingPollingCounter? _bytesWrittenPerSecondCounter; + IncrementingPollingCounter? _bytesReadPerSecondCounter; + + IncrementingPollingCounter? _commandsPerSecondCounter; + PollingCounter? _totalCommandsCounter; + PollingCounter? _failedCommandsCounter; + PollingCounter? _currentCommandsCounter; + PollingCounter? _preparedCommandsRatioCounter; + + PollingCounter? _poolsCounter; + PollingCounter? _idleConnectionsCounter; + PollingCounter? _busyConnectionsCounter; + + PollingCounter? _multiplexingAverageCommandsPerBatchCounter; + PollingCounter? _multiplexingAverageWriteTimePerBatchCounter; +#endif + + long _bytesWritten; + long _bytesRead; + + long _totalCommands; + long _totalPreparedCommands; + long _currentCommands; + long _failedCommands; + + readonly object _poolsLock = new(); + readonly HashSet _pools = new(); + + long _multiplexingBatchesSent; + long _multiplexingCommandsSent; + long _multiplexingTicksWritten; + + internal NpgsqlEventSource() : base(EventSourceName) {} + + // NOTE + // - The 'Start' and 'Stop' suffixes on the following event names have special meaning in EventSource. They + // enable creating 'activities'. + // For more information, take a look at the following blog post: + // https://blogs.msdn.microsoft.com/vancem/2015/09/14/exploring-eventsource-activity-correlation-and-causation-features/ + // - A stop event's event id must be next one after its start event. + + internal void BytesWritten(long bytesWritten) => Interlocked.Add(ref _bytesWritten, bytesWritten); + internal void BytesRead(long bytesRead) => Interlocked.Add(ref _bytesRead, bytesRead); + + public void CommandStart(string sql) + { + Interlocked.Increment(ref _totalCommands); + Interlocked.Increment(ref _currentCommands); + NpgsqlSqlEventSource.Log.CommandStart(sql); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public void CommandStop() + { + Interlocked.Decrement(ref _currentCommands); + NpgsqlSqlEventSource.Log.CommandStop(); + } + + internal void CommandStartPrepared() => Interlocked.Increment(ref _totalPreparedCommands); + + internal void CommandFailed() => Interlocked.Increment(ref _failedCommands); + + internal void PoolCreated(ConnectorSource pool) + { + lock (_poolsLock) + { + _pools.Add(pool); + } + } + + internal void MultiplexingBatchSent(int numCommands, Stopwatch stopwatch) + { + // TODO: CAS loop instead of 3 separate interlocked operations? + Interlocked.Increment(ref _multiplexingBatchesSent); + Interlocked.Add(ref _multiplexingCommandsSent, numCommands); + Interlocked.Add(ref _multiplexingTicksWritten, stopwatch.ElapsedTicks); + } + +#if !NETSTANDARD2_0 + double GetIdleConnections() + { + // Note: there's no attempt here to be coherent in terms of race conditions, especially not with regards + // to different counters. So idle and busy and be unsynchronized, as they're not polled together. + lock (_poolsLock) + { + var sum = 0; + foreach (var pool in _pools) + { + sum += pool.Statistics.Idle; + } + return sum; + } + } + + double GetBusyConnections() + { + // Note: there's no attempt here to be coherent in terms of race conditions, especially not with regards + // to different counters. So idle and busy and be unsynchronized, as they're not polled together. + lock (_poolsLock) + { + var sum = 0; + foreach (var pool in _pools) + { + sum += pool.Statistics.Busy; + } + return sum; + } + } + + double GetPoolsCount() + { + lock (_poolsLock) + { + return _pools.Count; + } + } + + double GetMultiplexingAverageCommandsPerBatch() + { + var batchesSent = Interlocked.Read(ref _multiplexingBatchesSent); + if (batchesSent == 0) + return -1; + + var commandsSent = (double)Interlocked.Read(ref _multiplexingCommandsSent); + return commandsSent / batchesSent; + } + + double GetMultiplexingAverageWriteTimePerBatch() + { + var batchesSent = Interlocked.Read(ref _multiplexingBatchesSent); + if (batchesSent == 0) + return -1; + + var ticksWritten = (double)Interlocked.Read(ref _multiplexingTicksWritten); + return ticksWritten / batchesSent / 1000; + } + + protected override void OnEventCommand(EventCommandEventArgs command) + { + if (command.Command == EventCommand.Enable) + { + // Comment taken from RuntimeEventSource in CoreCLR + // NOTE: These counters will NOT be disposed on disable command because we may be introducing + // a race condition by doing that. We still want to create these lazily so that we aren't adding + // overhead by at all times even when counters aren't enabled. + // On disable, PollingCounters will stop polling for values so it should be fine to leave them around. + + _bytesWrittenPerSecondCounter = new IncrementingPollingCounter("bytes-written-per-second", this, () => Interlocked.Read(ref _bytesWritten)) + { + DisplayName = "Bytes Written", + DisplayRateTimeScale = TimeSpan.FromSeconds(1) + }; + + _bytesReadPerSecondCounter = new IncrementingPollingCounter("bytes-read-per-second", this, () => Interlocked.Read(ref _bytesRead)) + { + DisplayName = "Bytes Read", + DisplayRateTimeScale = TimeSpan.FromSeconds(1) + }; + + _commandsPerSecondCounter = new IncrementingPollingCounter("commands-per-second", this, () => Interlocked.Read(ref _totalCommands)) + { + DisplayName = "Command Rate", + DisplayRateTimeScale = TimeSpan.FromSeconds(1) + }; + + _totalCommandsCounter = new PollingCounter("total-commands", this, () => Interlocked.Read(ref _totalCommands)) + { + DisplayName = "Total Commands", + }; + + _currentCommandsCounter = new PollingCounter("current-commands", this, () => Interlocked.Read(ref _currentCommands)) + { + DisplayName = "Current Commands" + }; + + _failedCommandsCounter = new PollingCounter("failed-commands", this, () => Interlocked.Read(ref _failedCommands)) + { + DisplayName = "Failed Commands" + }; + + _preparedCommandsRatioCounter = new PollingCounter( + "prepared-commands-ratio", + this, + () => (double)Interlocked.Read(ref _totalPreparedCommands) / Interlocked.Read(ref _totalCommands) * 100) + { + DisplayName = "Prepared Commands Ratio", + DisplayUnits = "%" + }; + + _poolsCounter = new PollingCounter("connection-pools", this, GetPoolsCount) + { + DisplayName = "Connection Pools" + }; + + _idleConnectionsCounter = new PollingCounter("idle-connections", this, GetIdleConnections) + { + DisplayName = "Idle Connections" + }; + + _busyConnectionsCounter = new PollingCounter("busy-connections", this, GetBusyConnections) + { + DisplayName = "Busy Connections" + }; + + _multiplexingAverageCommandsPerBatchCounter = new PollingCounter("multiplexing-average-commands-per-batch", this, GetMultiplexingAverageCommandsPerBatch) + { + DisplayName = "Average commands per multiplexing batch" + }; + + _multiplexingAverageWriteTimePerBatchCounter = new PollingCounter("multiplexing-average-write-time-per-batch", this, GetMultiplexingAverageWriteTimePerBatch) + { + DisplayName = "Average write time per multiplexing batch (us)", + DisplayUnits = "us" + }; + } + } +#endif +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlException.cs b/LibExternal/Npgsql/NpgsqlException.cs new file mode 100644 index 0000000..edafc70 --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlException.cs @@ -0,0 +1,76 @@ +using System; +using System.Data.Common; +using System.IO; +using System.Net.Sockets; +using System.Runtime.Serialization; + +namespace Npgsql; + +/// +/// The exception that is thrown when server-related issues occur. +/// +/// +/// PostgreSQL errors (e.g. query SQL issues, constraint violations) are raised via +/// which is a subclass of this class. +/// Purely Npgsql-related issues which aren't related to the server will be raised +/// via the standard CLR exceptions (e.g. ArgumentException). +/// +[Serializable] +public class NpgsqlException : DbException +{ + /// + /// Initializes a new instance of the class. + /// + public NpgsqlException() {} + + /// + /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public NpgsqlException(string? message, Exception? innerException) + : base(message, innerException) {} + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + public NpgsqlException(string? message) + : base(message) { } + + /// + /// Specifies whether the exception is considered transient, that is, whether retrying the operation could + /// succeed (e.g. a network error or a timeout). + /// +#if NET5_0_OR_GREATER + public override bool IsTransient +#else + public virtual bool IsTransient +#endif + => InnerException is IOException or SocketException or TimeoutException or NpgsqlException { IsTransient: true }; + +#if NET6_0_OR_GREATER + /// + public new NpgsqlBatchCommand? BatchCommand { get; set; } + + /// + protected override DbBatchCommand? DbBatchCommand => BatchCommand; +#else + /// + /// If the exception was thrown as a result of executing a , references the within + /// the batch which triggered the exception. Otherwise . + /// + public NpgsqlBatchCommand? BatchCommand { get; set; } +#endif + + #region Serialization + + /// + /// Initializes a new instance of the class with serialized data. + /// + /// The SerializationInfo that holds the serialized object data about the exception being thrown. + /// The StreamingContext that contains contextual information about the source or destination. + protected internal NpgsqlException(SerializationInfo info, StreamingContext context) : base(info, context) {} + + #endregion +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlFactory.cs b/LibExternal/Npgsql/NpgsqlFactory.cs new file mode 100644 index 0000000..8562f54 --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlFactory.cs @@ -0,0 +1,123 @@ +using System; +using System.Data.Common; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using Npgsql.Logging; + +namespace Npgsql; + +/// +/// A factory to create instances of various Npgsql objects. +/// +[Serializable] +public sealed class NpgsqlFactory : DbProviderFactory, IServiceProvider +{ + /// + /// Gets an instance of the . + /// This can be used to retrieve strongly typed data objects. + /// + public static readonly NpgsqlFactory Instance = new(); + + NpgsqlFactory() {} + + /// + /// Returns a strongly typed instance. + /// + public override DbCommand CreateCommand() => new NpgsqlCommand(); + + /// + /// Returns a strongly typed instance. + /// + public override DbConnection CreateConnection() => new NpgsqlConnection(); + + /// + /// Returns a strongly typed instance. + /// + public override DbParameter CreateParameter() => new NpgsqlParameter(); + + /// + /// Returns a strongly typed instance. + /// + public override DbConnectionStringBuilder CreateConnectionStringBuilder() => new NpgsqlConnectionStringBuilder(); + + /// + /// Returns a strongly typed instance. + /// + public override DbCommandBuilder CreateCommandBuilder() => new NpgsqlCommandBuilder(); + + /// + /// Returns a strongly typed instance. + /// + public override DbDataAdapter CreateDataAdapter() => new NpgsqlDataAdapter(); + +#if !NETSTANDARD2_0 + /// + /// Specifies whether the specific supports the class. + /// + public override bool CanCreateDataAdapter => true; + + /// + /// Specifies whether the specific supports the class. + /// + public override bool CanCreateCommandBuilder => true; +#endif + +#if NET6_0_OR_GREATER + /// + public override bool CanCreateBatch => true; + + /// + public override DbBatch CreateBatch() => new NpgsqlBatch(); + + /// + public override DbBatchCommand CreateBatchCommand() => new NpgsqlBatchCommand(); +#endif + + #region IServiceProvider Members + + /// + /// Gets the service object of the specified type. + /// + /// An object that specifies the type of service object to get. + /// A service object of type serviceType, or null if there is no service object of type serviceType. + [RequiresUnreferencedCode("Legacy EF5 method, not trimming-safe.")] + public object? GetService(Type serviceType) + { + if (serviceType == null) + throw new ArgumentNullException(nameof(serviceType)); + + // In legacy Entity Framework, this is the entry point for obtaining Npgsql's + // implementation of DbProviderServices. We use reflection for all types to + // avoid any dependencies on EF stuff in this project. EF6 (and of course EF Core) do not use this method. + + if (serviceType.FullName != "System.Data.Common.DbProviderServices") + return null; + + // User has requested a legacy EF DbProviderServices implementation. Check our cache first. + if (_legacyEntityFrameworkServices != null) + return _legacyEntityFrameworkServices; + + // First time, attempt to find the EntityFramework5.Npgsql assembly and load the type via reflection + var assemblyName = typeof(NpgsqlFactory).GetTypeInfo().Assembly.GetName(); + assemblyName.Name = "EntityFramework5.Npgsql"; + Assembly npgsqlEfAssembly; + try { + npgsqlEfAssembly = Assembly.Load(new AssemblyName(assemblyName.FullName)); + } catch { + return null; + } + + Type? npgsqlServicesType; + if ((npgsqlServicesType = npgsqlEfAssembly.GetType("Npgsql.NpgsqlServices")) == null || + npgsqlServicesType.GetProperty("Instance") == null) + throw new Exception("EntityFramework5.Npgsql assembly does not seem to contain the correct type!"); + + return _legacyEntityFrameworkServices = npgsqlServicesType + .GetProperty("Instance", BindingFlags.Public | BindingFlags.Static)! + .GetMethod!.Invoke(null, new object[0]); + } + + static object? _legacyEntityFrameworkServices; + + #endregion +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlNestedDataReader.cs b/LibExternal/Npgsql/NpgsqlNestedDataReader.cs new file mode 100644 index 0000000..2f1c95e --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlNestedDataReader.cs @@ -0,0 +1,514 @@ +using Npgsql.Internal; +using Npgsql.Internal.TypeHandlers; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using Npgsql.TypeMapping; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data.Common; +using System.Globalization; +using System.IO; +using System.Runtime.CompilerServices; + +namespace Npgsql; + +/// +/// Reads a forward-only stream of rows from a nested data source. +/// Can be retrieved using or +/// . +/// +public sealed class NpgsqlNestedDataReader : DbDataReader +{ + readonly NpgsqlDataReader _outermostReader; + ulong _uniqueOutermostReaderRowId; + readonly NpgsqlNestedDataReader? _outerNestedReader; + NpgsqlNestedDataReader? _cachedFreeNestedDataReader; + PostgresCompositeType? _compositeType; + readonly int _depth; + int _numRows; + int _nextRowIndex; + int _nextRowBufferPos; + ReaderState _readerState; + + readonly List _columns = new(); + + readonly struct ColumnInfo + { + public readonly uint TypeOid; + public readonly int BufferPos; + public readonly NpgsqlTypeHandler TypeHandler; + + public ColumnInfo(uint typeOid, int bufferPos, NpgsqlTypeHandler typeHandler) + { + TypeOid = typeOid; + BufferPos = bufferPos; + TypeHandler = typeHandler; + } + } + + NpgsqlReadBuffer Buffer => _outermostReader.Buffer; + ConnectorTypeMapper TypeMapper => _outermostReader.Connector.TypeMapper; + + internal NpgsqlNestedDataReader(NpgsqlDataReader outermostReader, NpgsqlNestedDataReader? outerNestedReader, + ulong uniqueOutermostReaderRowId, int depth, PostgresCompositeType? compositeType) + { + _outermostReader = outermostReader; + _outerNestedReader = outerNestedReader; + _uniqueOutermostReaderRowId = uniqueOutermostReaderRowId; + _depth = depth; + _compositeType = compositeType; + } + + internal void Init(ulong uniqueOutermostReaderRowId, PostgresCompositeType? compositeType) + { + _uniqueOutermostReaderRowId = uniqueOutermostReaderRowId; + _columns.Clear(); + _numRows = 0; + _nextRowIndex = 0; + _nextRowBufferPos = 0; + _readerState = ReaderState.BeforeFirstRow; + _compositeType = compositeType; + } + + internal void InitArray() + { + var dimensions = Buffer.ReadInt32(); + var containsNulls = Buffer.ReadInt32() == 1; + Buffer.ReadUInt32(); // Element OID. Ignored. + + if (containsNulls) + throw new InvalidOperationException("Record array contains null record"); + + if (dimensions == 0) + return; + + if (dimensions != 1) + throw new InvalidOperationException("Cannot read a multidimensional array with a nested DbDataReader"); + + _numRows = Buffer.ReadInt32(); + Buffer.ReadInt32(); // Lower bound + + if (_numRows > 0) + Buffer.ReadInt32(); // Length of first row + + _nextRowBufferPos = Buffer.ReadPosition; + } + + internal void InitSingleRow() + { + _numRows = 1; + _nextRowBufferPos = Buffer.ReadPosition; + } + + /// + public override object this[int ordinal] => GetValue(ordinal); + + /// + public override object this[string name] => GetValue(GetOrdinal(name)); + + /// + public override int Depth + { + get + { + CheckNotClosed(); + return _depth; + } + } + + /// + public override int FieldCount + { + get + { + CheckNotClosed(); + return _readerState == ReaderState.OnRow ? _columns.Count : 0; + } + } + + /// + public override bool HasRows + { + get + { + CheckNotClosed(); + return _numRows > 0; + } + } + + /// + public override bool IsClosed + => _readerState == ReaderState.Closed || _readerState == ReaderState.Disposed + || _outermostReader.IsClosed || _uniqueOutermostReaderRowId != _outermostReader.UniqueRowId; + + /// + public override int RecordsAffected => -1; + + /// + public override bool GetBoolean(int ordinal) => GetFieldValue(ordinal); + /// + public override byte GetByte(int ordinal) => GetFieldValue(ordinal); + /// + public override char GetChar(int ordinal) => GetFieldValue(ordinal); + /// + public override DateTime GetDateTime(int ordinal) => GetFieldValue(ordinal); + /// + public override decimal GetDecimal(int ordinal) => GetFieldValue(ordinal); + /// + public override double GetDouble(int ordinal) => GetFieldValue(ordinal); + /// + public override float GetFloat(int ordinal) => GetFieldValue(ordinal); + /// + public override Guid GetGuid(int ordinal) => GetFieldValue(ordinal); + /// + public override short GetInt16(int ordinal) => GetFieldValue(ordinal); + /// + public override int GetInt32(int ordinal) => GetFieldValue(ordinal); + /// + public override long GetInt64(int ordinal) => GetFieldValue(ordinal); + /// + public override string GetString(int ordinal) => GetFieldValue(ordinal); + + /// + public override long GetBytes(int ordinal, long dataOffset, byte[]? buffer, int bufferOffset, int length) + { + if (dataOffset < 0 || dataOffset > int.MaxValue) + throw new ArgumentOutOfRangeException(nameof(dataOffset), dataOffset, $"dataOffset must be between {0} and {int.MaxValue}"); + if (buffer != null && (bufferOffset < 0 || bufferOffset >= buffer.Length + 1)) + throw new IndexOutOfRangeException($"bufferOffset must be between {0} and {(buffer.Length)}"); + if (buffer != null && (length < 0 || length > buffer.Length - bufferOffset)) + throw new IndexOutOfRangeException($"length must be between {0} and {buffer.Length - bufferOffset}"); + + var field = CheckRowAndColumnAndSeek(ordinal); + var handler = field.Handler; + if (!(handler is ByteaHandler)) + throw new InvalidCastException("GetBytes() not supported for type " + field.Handler.PgDisplayName); + + if (field.Length == -1) + throw new InvalidCastException("field is null"); + + var dataOffset2 = (int)dataOffset; + if (dataOffset2 > field.Length) + throw new ArgumentOutOfRangeException(nameof(dataOffset), + $"attempting to read out of bounds from the column data, dataOffset must be between {0} and {field.Length}"); + + Buffer.ReadPosition += dataOffset2; + + length = Math.Min(length, field.Length - dataOffset2); + + if (buffer == null) + return length; + + return Buffer.Read(new Span(buffer, bufferOffset, length)); + } + /// + public override long GetChars(int ordinal, long dataOffset, char[]? buffer, int bufferOffset, int length) + => throw new NotSupportedException(); + + /// + protected override DbDataReader GetDbDataReader(int ordinal) => GetData(ordinal); + + /// + /// Returns a nested data reader for the requested column. + /// The column type must be a record or a to Npgsql known composite type, or an array thereof. + /// + /// The zero-based column ordinal. + /// A data reader. + public new NpgsqlNestedDataReader GetData(int ordinal) + { + var field = CheckRowAndColumnAndSeek(ordinal); + var type = field.Handler.PostgresType; + var isArray = type is PostgresArrayType; + var elementType = isArray ? ((PostgresArrayType)type).Element : type; + var compositeType = elementType as PostgresCompositeType; + if (elementType.InternalName != "record" && compositeType == null) + throw new InvalidCastException("GetData() not supported for type " + type.DisplayName); + + if (field.Length == -1) + throw new InvalidCastException("field is null"); + + var reader = _cachedFreeNestedDataReader; + if (reader != null) + { + _cachedFreeNestedDataReader = null; + reader.Init(_uniqueOutermostReaderRowId, compositeType); + } + else + { + reader = new NpgsqlNestedDataReader(_outermostReader, this, _uniqueOutermostReaderRowId, _depth + 1, compositeType); + } + if (isArray) + reader.InitArray(); + else + reader.InitSingleRow(); + return reader; + } + + /// + public override string GetDataTypeName(int ordinal) + { + var column = CheckRowAndColumn(ordinal); + return column.TypeHandler.PgDisplayName; + } + + /// + public override IEnumerator GetEnumerator() => new DbEnumerator(this); + + /// + public override string GetName(int ordinal) + { + CheckRowAndColumn(ordinal); + return _compositeType?.Fields[ordinal].Name ?? "?column?"; + } + + /// + public override int GetOrdinal(string name) + { + if (_compositeType == null) + throw new NotSupportedException("GetOrdinal is not supported for the record type"); + + for (var i = 0; i < _compositeType.Fields.Count; i++) + { + if (_compositeType.Fields[i].Name == name) + return i; + } + + for (var i = 0; i < _compositeType.Fields.Count; i++) + { + if (string.Compare(_compositeType.Fields[i].Name, name, CultureInfo.InvariantCulture, + CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase | CompareOptions.IgnoreKanaType) == 0) + return i; + } + + throw new IndexOutOfRangeException("Field not found in row: " + name); + } + + /// + public override Type GetFieldType(int ordinal) + { + var column = CheckRowAndColumn(ordinal); + return column.TypeHandler.GetFieldType(); + } + + /// + public override object GetValue(int ordinal) + { + var column = CheckRowAndColumnAndSeek(ordinal); + if (column.Length == -1) + return DBNull.Value; + return column.Handler.ReadAsObject(Buffer, column.Length); + } + + /// + public override int GetValues(object[] values) + { + if (values == null) + throw new ArgumentNullException(nameof(values)); + CheckOnRow(); + + var count = Math.Min(FieldCount, values.Length); + for (var i = 0; i < count; i++) + values[i] = GetValue(i); + return count; + } + + /// + public override bool IsDBNull(int ordinal) + => CheckRowAndColumnAndSeek(ordinal).Length == -1; + + /// + public override T GetFieldValue(int ordinal) + { + if (typeof(T) == typeof(Stream)) + return (T)(object)GetStream(ordinal); + + if (typeof(T) == typeof(TextReader)) + return (T)(object)GetTextReader(ordinal); + + var field = CheckRowAndColumnAndSeek(ordinal); + + if (field.Length == -1) + { + // When T is a Nullable (and only in that case), we support returning null + if (NullableHandler.Exists) + return default!; + + if (typeof(T) == typeof(object)) + return (T)(object)DBNull.Value; + + throw new InvalidCastException("field is null"); + } + + return NullableHandler.Exists + ? NullableHandler.Read(field.Handler, Buffer, field.Length, fieldDescription: null) + : typeof(T) == typeof(object) + ? (T)field.Handler.ReadAsObject(Buffer, field.Length, fieldDescription: null) + : field.Handler.Read(Buffer, field.Length, fieldDescription: null); + } + + /// + public override Type GetProviderSpecificFieldType(int ordinal) + { + var column = CheckRowAndColumn(ordinal); + return column.TypeHandler.GetProviderSpecificFieldType(); + } + + /// + public override object GetProviderSpecificValue(int ordinal) + { + var column = CheckRowAndColumnAndSeek(ordinal); + if (column.Length == -1) + return DBNull.Value; + return column.Handler.ReadPsvAsObject(Buffer, column.Length); + } + + /// + public override int GetProviderSpecificValues(object[] values) + { + if (values == null) + throw new ArgumentNullException(nameof(values)); + CheckOnRow(); + + var count = Math.Min(FieldCount, values.Length); + for (var i = 0; i < count; i++) + values[i] = GetProviderSpecificValue(i); + return count; + } + + /// + public override bool Read() + { + CheckResultSet(); + + Buffer.ReadPosition = _nextRowBufferPos; + if (_nextRowIndex == _numRows) + { + _readerState = ReaderState.AfterRows; + return false; + } + + if (_nextRowIndex++ != 0) + Buffer.ReadInt32(); // Length of record + + var numColumns = Buffer.ReadInt32(); + + for (var i = 0; i < numColumns; i++) + { + var typeOid = Buffer.ReadUInt32(); + var bufferPos = Buffer.ReadPosition; + if (i >= _columns.Count) + _columns.Add(new ColumnInfo(typeOid, bufferPos, TypeMapper.ResolveByOID(typeOid))); + else + _columns[i] = new ColumnInfo(typeOid, bufferPos, + _columns[i].TypeOid == typeOid ? _columns[i].TypeHandler : TypeMapper.ResolveByOID(typeOid)); + + var columnLen = Buffer.ReadInt32(); + if (columnLen >= 0) + Buffer.Skip(columnLen); + } + _columns.RemoveRange(numColumns, _columns.Count - numColumns); + + _nextRowBufferPos = Buffer.ReadPosition; + + _readerState = ReaderState.OnRow; + return true; + } + + /// + public override bool NextResult() + { + CheckNotClosed(); + + _numRows = 0; + _nextRowBufferPos = 0; + _nextRowIndex = 0; + _readerState = ReaderState.AfterResult; + return false; + } + + /// + public override void Close() + { + if (_readerState != ReaderState.Disposed) + { + _readerState = ReaderState.Closed; + } + } + + /// + protected override void Dispose(bool disposing) + { + if (disposing && _readerState != ReaderState.Disposed) + { + Close(); + _readerState = ReaderState.Disposed; + if (_outerNestedReader != null) + { + _outerNestedReader._cachedFreeNestedDataReader ??= this; + } + else + { + _outermostReader.CachedFreeNestedDataReader ??= this; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void CheckNotClosed() + { + if (IsClosed) + throw new InvalidOperationException("The reader is closed"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void CheckResultSet() + { + CheckNotClosed(); + switch (_readerState) + { + case ReaderState.BeforeFirstRow: + case ReaderState.OnRow: + case ReaderState.AfterRows: + break; + default: + throw new InvalidOperationException("No resultset is currently being traversed"); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void CheckOnRow() + { + CheckResultSet(); + if (_readerState != ReaderState.OnRow) + throw new InvalidOperationException("No row is available"); + } + + ColumnInfo CheckRowAndColumn(int column) + { + CheckOnRow(); + + if (column < 0 || column >= _columns.Count) + throw new IndexOutOfRangeException($"Column must be between {0} and {_columns.Count - 1}"); + + return _columns[column]; + } + + (NpgsqlTypeHandler Handler, int Length) CheckRowAndColumnAndSeek(int ordinal) + { + var column = CheckRowAndColumn(ordinal); + Buffer.ReadPosition = column.BufferPos; + var len = Buffer.ReadInt32(); + return (column.TypeHandler, len); + } + + enum ReaderState + { + BeforeFirstRow, + OnRow, + AfterRows, + AfterResult, + Closed, + Disposed + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlNotificationEventArgs.cs b/LibExternal/Npgsql/NpgsqlNotificationEventArgs.cs new file mode 100644 index 0000000..454ccdc --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlNotificationEventArgs.cs @@ -0,0 +1,47 @@ +using System; +using Npgsql.Internal; + +namespace Npgsql; + +/// +/// Provides information on a PostgreSQL notification. Notifications are sent when your connection has registered for +/// notifications on a specific channel via the LISTEN command. NOTIFY can be used to generate such notifications, +/// allowing for an inter-connection communication channel. +/// +public sealed class NpgsqlNotificationEventArgs : EventArgs +{ + /// + /// Process ID of the PostgreSQL backend that sent this notification. + /// + // ReSharper disable once InconsistentNaming + public int PID { get; } + + /// + /// The channel on which the notification was sent. + /// + public string Channel { get; } + + /// + /// An optional payload string that was sent with this notification. + /// + public string Payload { get; } + + /// + /// The channel on which the notification was sent. + /// + [Obsolete("Use Channel instead")] + public string Condition => Channel; + + /// + /// An optional payload string that was sent with this notification. + /// + [Obsolete("Use Payload instead")] + public string AdditionalInformation => Payload; + + internal NpgsqlNotificationEventArgs(NpgsqlReadBuffer buf) + { + PID = buf.ReadInt32(); + Channel = buf.ReadNullTerminatedString(); + Payload = buf.ReadNullTerminatedString(); + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlOperationInProgressException.cs b/LibExternal/Npgsql/NpgsqlOperationInProgressException.cs new file mode 100644 index 0000000..eb7377a --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlOperationInProgressException.cs @@ -0,0 +1,34 @@ +using Npgsql.Internal; + +namespace Npgsql; + +/// +/// Thrown when trying to use a connection that is already busy performing some other operation. +/// Provides information on the already-executing operation to help with debugging. +/// +public sealed class NpgsqlOperationInProgressException : NpgsqlException +{ + /// + /// Creates a new instance of . + /// + /// + /// A command which was in progress when the operation which triggered this exception was executed. + /// + public NpgsqlOperationInProgressException(NpgsqlCommand command) + : base("A command is already in progress: " + command.CommandText) + { + CommandInProgress = command; + } + + internal NpgsqlOperationInProgressException(ConnectorState state) + : base($"The connection is already in state '{state}'") + { + } + + /// + /// If the connection is busy with another command, this will contain a reference to that command. + /// Otherwise, if the connection if busy with another type of operation (e.g. COPY), contains + /// . + /// + public NpgsqlCommand? CommandInProgress { get; } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlParameter.cs b/LibExternal/Npgsql/NpgsqlParameter.cs new file mode 100644 index 0000000..594c504 --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlParameter.cs @@ -0,0 +1,590 @@ +using System; +using System.ComponentModel; +using System.Data; +using System.Data.Common; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.Internal; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using Npgsql.TypeMapping; +using Npgsql.Util; +using NpgsqlTypes; +using static Npgsql.Util.Statics; + +namespace Npgsql; + +/// +/// This class represents a parameter to a command that will be sent to server +/// +public class NpgsqlParameter : DbParameter, IDbDataParameter, ICloneable +{ + #region Fields and Properties + + private protected byte _precision; + private protected byte _scale; + private protected int _size; + + // ReSharper disable InconsistentNaming + private protected NpgsqlDbType? _npgsqlDbType; + private protected string? _dataTypeName; + // ReSharper restore InconsistentNaming + + private protected string _name = string.Empty; + private protected object? _value; + private protected string _sourceColumn; + + internal string TrimmedName { get; private protected set; } = PositionalName; + internal const string PositionalName = ""; + + /// + /// Can be used to communicate a value from the validation phase to the writing phase. + /// To be used by type handlers only. + /// + public object? ConvertedValue { get; set; } + + internal NpgsqlLengthCache? LengthCache { get; set; } + + internal NpgsqlTypeHandler? Handler { get; set; } + + internal FormatCode FormatCode { get; private set; } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public NpgsqlParameter() + { + _sourceColumn = string.Empty; + Direction = ParameterDirection.Input; + SourceVersion = DataRowVersion.Current; + } + + /// + /// Initializes a new instance of the class with the parameter name and a value. + /// + /// The name of the parameter to map. + /// The value of the . + /// + ///

+ /// When you specify an in the value parameter, the is + /// inferred from the CLR type. + ///

+ ///

+ /// When using this constructor, you must be aware of a possible misuse of the constructor which takes a + /// parameter. This happens when calling this constructor passing an int 0 and the compiler thinks you are passing a value of + /// . Use for example to have compiler calling the correct constructor. + ///

+ ///
+ public NpgsqlParameter(string? parameterName, object? value) + : this() + { + ParameterName = parameterName; + // ReSharper disable once VirtualMemberCallInConstructor + Value = value; + } + + /// + /// Initializes a new instance of the class with the parameter name and the data type. + /// + /// The name of the parameter to map. + /// One of the values. + public NpgsqlParameter(string? parameterName, NpgsqlDbType parameterType) + : this(parameterName, parameterType, 0, string.Empty) + { + } + + /// + /// Initializes a new instance of the . + /// + /// The name of the parameter to map. + /// One of the values. + public NpgsqlParameter(string? parameterName, DbType parameterType) + : this(parameterName, parameterType, 0, string.Empty) + { + } + + /// + /// Initializes a new instance of the . + /// + /// The name of the parameter to map. + /// One of the values. + /// The length of the parameter. + public NpgsqlParameter(string? parameterName, NpgsqlDbType parameterType, int size) + : this(parameterName, parameterType, size, string.Empty) + { + } + + /// + /// Initializes a new instance of the . + /// + /// The name of the parameter to map. + /// One of the values. + /// The length of the parameter. + public NpgsqlParameter(string? parameterName, DbType parameterType, int size) + : this(parameterName, parameterType, size, string.Empty) + { + } + + /// + /// Initializes a new instance of the + /// + /// The name of the parameter to map. + /// One of the values. + /// The length of the parameter. + /// The name of the source column. + public NpgsqlParameter(string? parameterName, NpgsqlDbType parameterType, int size, string? sourceColumn) + { + ParameterName = parameterName; + NpgsqlDbType = parameterType; + _size = size; + _sourceColumn = sourceColumn ?? string.Empty; + Direction = ParameterDirection.Input; + SourceVersion = DataRowVersion.Current; + } + + /// + /// Initializes a new instance of the . + /// + /// The name of the parameter to map. + /// One of the values. + /// The length of the parameter. + /// The name of the source column. + public NpgsqlParameter(string? parameterName, DbType parameterType, int size, string? sourceColumn) + { + ParameterName = parameterName; + DbType = parameterType; + _size = size; + _sourceColumn = sourceColumn ?? string.Empty; + Direction = ParameterDirection.Input; + SourceVersion = DataRowVersion.Current; + } + + /// + /// Initializes a new instance of the . + /// + /// The name of the parameter to map. + /// One of the values. + /// The length of the parameter. + /// The name of the source column. + /// One of the values. + /// + /// if the value of the field can be , otherwise . + /// + /// + /// The total number of digits to the left and right of the decimal point to which is resolved. + /// + /// The total number of decimal places to which is resolved. + /// One of the values. + /// An that is the value of the . + public NpgsqlParameter(string parameterName, NpgsqlDbType parameterType, int size, string? sourceColumn, + ParameterDirection direction, bool isNullable, byte precision, byte scale, + DataRowVersion sourceVersion, object value) + { + ParameterName = parameterName; + Size = size; + _sourceColumn = sourceColumn ?? string.Empty; + Direction = direction; + IsNullable = isNullable; + Precision = precision; + Scale = scale; + SourceVersion = sourceVersion; + // ReSharper disable once VirtualMemberCallInConstructor + Value = value; + + NpgsqlDbType = parameterType; + } + + /// + /// Initializes a new instance of the . + /// + /// The name of the parameter to map. + /// One of the values. + /// The length of the parameter. + /// The name of the source column. + /// One of the values. + /// + /// if the value of the field can be , otherwise . + /// + /// + /// The total number of digits to the left and right of the decimal point to which is resolved. + /// + /// The total number of decimal places to which is resolved. + /// One of the values. + /// An that is the value of the . + public NpgsqlParameter(string parameterName, DbType parameterType, int size, string? sourceColumn, + ParameterDirection direction, bool isNullable, byte precision, byte scale, + DataRowVersion sourceVersion, object value) + { + ParameterName = parameterName; + Size = size; + _sourceColumn = sourceColumn ?? string.Empty; + Direction = direction; + IsNullable = isNullable; + Precision = precision; + Scale = scale; + SourceVersion = sourceVersion; + // ReSharper disable once VirtualMemberCallInConstructor + Value = value; + DbType = parameterType; + } + #endregion + + #region Name + + /// + /// Gets or sets The name of the . + /// + /// The name of the . + /// The default is an empty string. + [AllowNull, DefaultValue("")] + public sealed override string ParameterName + { + get => _name; + set + { + if (Collection is not null) + Collection.ChangeParameterName(this, value); + else + ChangeParameterName(value); + } + } + + internal void ChangeParameterName(string? value) + { + if (value == null) + _name = TrimmedName = PositionalName; + else if (value.Length > 0 && (value[0] == ':' || value[0] == '@')) + TrimmedName = (_name = value).Substring(1); + else + _name = TrimmedName = value; + } + + internal bool IsPositional => ParameterName.Length == 0; + + #endregion Name + + #region Value + + /// + [TypeConverter(typeof(StringConverter)), Category("Data")] + public override object? Value + { + get => _value; + set + { + if (_value == null || value == null || _value.GetType() != value.GetType()) + Handler = null; + _value = value; + ConvertedValue = null; + } + } + + /// + /// Gets or sets the value of the parameter. + /// + /// + /// An that is the value of the parameter. + /// The default value is . + /// + [Category("Data")] + [TypeConverter(typeof(StringConverter))] + public object? NpgsqlValue + { + get => Value; + set => Value = value; + } + + #endregion Value + + #region Type + + /// + /// Gets or sets the of the parameter. + /// + /// One of the values. The default is . + [DefaultValue(DbType.Object)] + [Category("Data"), RefreshProperties(RefreshProperties.All)] + public sealed override DbType DbType + { + get + { + if (_npgsqlDbType.HasValue) + return GlobalTypeMapper.NpgsqlDbTypeToDbType(_npgsqlDbType.Value); + + if (_dataTypeName is not null) + return GlobalTypeMapper.NpgsqlDbTypeToDbType(GlobalTypeMapper.DataTypeNameToNpgsqlDbType(_dataTypeName)); + + if (Value is not null) // Infer from value but don't cache + { + return GlobalTypeMapper.Instance.TryResolveMappingByValue(Value, out var mapping) + ? mapping.DbType + : DbType.Object; + } + + return DbType.Object; + } + set + { + Handler = null; + _npgsqlDbType = value == DbType.Object + ? null + : GlobalTypeMapper.DbTypeToNpgsqlDbType(value) + ?? throw new NotSupportedException($"The parameter type DbType.{value} isn't supported by PostgreSQL or Npgsql"); + } + } + + /// + /// Gets or sets the of the parameter. + /// + /// One of the values. The default is . + [DefaultValue(NpgsqlDbType.Unknown)] + [Category("Data"), RefreshProperties(RefreshProperties.All)] + [DbProviderSpecificTypeProperty(true)] + public NpgsqlDbType NpgsqlDbType + { + [RequiresUnreferencedCodeAttribute("The NpgsqlDbType getter isn't trimming-safe")] + get + { + if (_npgsqlDbType.HasValue) + return _npgsqlDbType.Value; + + if (_dataTypeName is not null) + return GlobalTypeMapper.DataTypeNameToNpgsqlDbType(_dataTypeName); + + if (Value is not null) // Infer from value + { + return GlobalTypeMapper.Instance.TryResolveMappingByValue(Value, out var mapping) + ? mapping.NpgsqlDbType ?? NpgsqlDbType.Unknown + : throw new NotSupportedException("Can't infer NpgsqlDbType for type " + Value.GetType()); + } + + return NpgsqlDbType.Unknown; + } + set + { + if (value == NpgsqlDbType.Array) + throw new ArgumentOutOfRangeException(nameof(value), "Cannot set NpgsqlDbType to just Array, Binary-Or with the element type (e.g. Array of Box is NpgsqlDbType.Array | NpgsqlDbType.Box)."); + if (value == NpgsqlDbType.Range) + throw new ArgumentOutOfRangeException(nameof(value), "Cannot set NpgsqlDbType to just Range, Binary-Or with the element type (e.g. Range of integer is NpgsqlDbType.Range | NpgsqlDbType.Integer)"); + + Handler = null; + _npgsqlDbType = value; + } + } + + /// + /// Used to specify which PostgreSQL type will be sent to the database for this parameter. + /// + public string? DataTypeName + { + get + { + if (_dataTypeName != null) + return _dataTypeName; + + if (_npgsqlDbType.HasValue) + return GlobalTypeMapper.NpgsqlDbTypeToDataTypeName(_npgsqlDbType.Value); + + if (Value != null) // Infer from value + { + return GlobalTypeMapper.Instance.TryResolveMappingByValue(Value, out var mapping) + ? mapping.DataTypeName + : null; + } + + return null; + } + set + { + _dataTypeName = value; + Handler = null; + } + } + + #endregion Type + + #region Other Properties + + /// + public sealed override bool IsNullable { get; set; } + + /// + [DefaultValue(ParameterDirection.Input)] + [Category("Data")] + public sealed override ParameterDirection Direction { get; set; } + +#pragma warning disable CS0109 + /// + /// Gets or sets the maximum number of digits used to represent the property. + /// + /// + /// The maximum number of digits used to represent the property. + /// The default value is 0, which indicates that the data provider sets the precision for . + [DefaultValue((byte)0)] + [Category("Data")] + public new byte Precision + { + get => _precision; + set + { + _precision = value; + Handler = null; + } + } + + /// + /// Gets or sets the number of decimal places to which is resolved. + /// + /// The number of decimal places to which is resolved. The default is 0. + [DefaultValue((byte)0)] + [Category("Data")] + public new byte Scale + { + get => _scale; + set + { + _scale = value; + Handler = null; + } + } +#pragma warning restore CS0109 + + /// + [DefaultValue(0)] + [Category("Data")] + public sealed override int Size + { + get => _size; + set + { + if (value < -1) + throw new ArgumentException($"Invalid parameter Size value '{value}'. The value must be greater than or equal to 0."); + + _size = value; + Handler = null; + } + } + + /// + [AllowNull, DefaultValue("")] + [Category("Data")] + public sealed override string SourceColumn + { + get => _sourceColumn; + set => _sourceColumn = value ?? string.Empty; + } + + /// + [Category("Data"), DefaultValue(DataRowVersion.Current)] + public sealed override DataRowVersion SourceVersion { get; set; } + + /// + public sealed override bool SourceColumnNullMapping { get; set; } + +#pragma warning disable CA2227 + /// + /// The collection to which this parameter belongs, if any. + /// + public NpgsqlParameterCollection? Collection { get; set; } +#pragma warning restore CA2227 + + /// + /// The PostgreSQL data type, such as int4 or text, as discovered from pg_type. + /// This property is automatically set if parameters have been derived via + /// and can be used to + /// acquire additional information about the parameters' data type. + /// + public PostgresType? PostgresType { get; internal set; } + + #endregion Other Properties + + #region Internals + + internal virtual void ResolveHandler(ConnectorTypeMapper typeMapper) + { + if (Handler is not null) + return; + + if (_npgsqlDbType.HasValue) + Handler = typeMapper.ResolveByNpgsqlDbType(_npgsqlDbType.Value); + else if (_dataTypeName is not null) + Handler = typeMapper.ResolveByDataTypeName(_dataTypeName); + else if (_value is not null) + Handler = typeMapper.ResolveByValue(_value); + else + throw new InvalidOperationException($"Parameter '{ParameterName}' must have its value set"); + } + + internal void Bind(ConnectorTypeMapper typeMapper) + { + ResolveHandler(typeMapper); + FormatCode = Handler!.PreferTextWrite ? FormatCode.Text : FormatCode.Binary; + } + + internal virtual int ValidateAndGetLength() + { + if (_value is DBNull) + return 0; + if (_value == null) + throw new InvalidCastException($"Parameter {ParameterName} must be set"); + + var lengthCache = LengthCache; + var len = Handler!.ValidateObjectAndGetLength(_value, ref lengthCache, this); + LengthCache = lengthCache; + return len; + } + + internal virtual Task WriteWithLength(NpgsqlWriteBuffer buf, bool async, CancellationToken cancellationToken = default) + => Handler!.WriteObjectWithLength(_value!, buf, LengthCache, this, async, cancellationToken); + + /// + public override void ResetDbType() + { + _npgsqlDbType = null; + _dataTypeName = null; + Handler = null; + } + + internal bool IsInputDirection => Direction == ParameterDirection.InputOutput || Direction == ParameterDirection.Input; + + internal bool IsOutputDirection => Direction == ParameterDirection.InputOutput || Direction == ParameterDirection.Output; + + #endregion + + #region Clone + + /// + /// Creates a new that is a copy of the current instance. + /// + /// A new that is a copy of this instance. + public NpgsqlParameter Clone() => CloneCore(); + + private protected virtual NpgsqlParameter CloneCore() => + // use fields instead of properties + // to avoid auto-initializing something like type_info + new() + { + _precision = _precision, + _scale = _scale, + _size = _size, + _npgsqlDbType = _npgsqlDbType, + _dataTypeName = _dataTypeName, + Direction = Direction, + IsNullable = IsNullable, + _name = _name, + TrimmedName = TrimmedName, + SourceColumn = SourceColumn, + SourceVersion = SourceVersion, + _value = _value, + SourceColumnNullMapping = SourceColumnNullMapping, + }; + + object ICloneable.Clone() => Clone(); + + #endregion +} diff --git a/LibExternal/Npgsql/NpgsqlParameterCollection.cs b/LibExternal/Npgsql/NpgsqlParameterCollection.cs new file mode 100644 index 0000000..850f481 --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlParameterCollection.cs @@ -0,0 +1,808 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Npgsql.TypeMapping; +using NpgsqlTypes; + +namespace Npgsql; + +/// +/// Represents a collection of parameters relevant to a as well as their respective mappings to columns in +/// a . +/// +public sealed class NpgsqlParameterCollection : DbParameterCollection, IList +{ + internal const int LookupThreshold = 5; + + internal List InternalList { get; } = new(5); +#if DEBUG + internal static bool TwoPassCompatMode; +#else + internal static readonly bool TwoPassCompatMode; +#endif + + static NpgsqlParameterCollection() + => TwoPassCompatMode = AppContext.TryGetSwitch("Npgsql.EnableLegacyCaseInsensitiveDbParameters", out var enabled) + && enabled; + + // Dictionary lookups for GetValue to improve performance. _caseSensitiveLookup is only ever used in legacy two-pass mode. + Dictionary? _caseInsensitiveLookup; + Dictionary? _caseSensitiveLookup; + + /// + /// Initializes a new instance of the NpgsqlParameterCollection class. + /// + internal NpgsqlParameterCollection() + { + } + + bool LookupEnabled => InternalList.Count >= LookupThreshold; + + void LookupClear() + { + _caseInsensitiveLookup?.Clear(); + _caseSensitiveLookup?.Clear(); + } + + void LookupAdd(string name, int index) + { + if (_caseInsensitiveLookup is null) + return; + + if (TwoPassCompatMode && !_caseSensitiveLookup!.ContainsKey(name)) + _caseSensitiveLookup[name] = index; + + if (!_caseInsensitiveLookup.ContainsKey(name)) + _caseInsensitiveLookup[name] = index; + } + + void LookupInsert(string name, int index) + { + if (_caseInsensitiveLookup is null) + return; + + if (TwoPassCompatMode && + (!_caseSensitiveLookup!.TryGetValue(name, out var indexCs) || index < indexCs)) + { + for (var i = index + 1; i < InternalList.Count; i++) + { + var parameterName = InternalList[i].TrimmedName; + if (_caseSensitiveLookup.TryGetValue(parameterName, out var currentI) && currentI + 1 == i) + _caseSensitiveLookup[parameterName] = i; + } + + _caseSensitiveLookup[name] = index; + } + + if (!_caseInsensitiveLookup.TryGetValue(name, out var indexCi) || index < indexCi) + { + for (var i = index + 1; i < InternalList.Count; i++) + { + var parameterName = InternalList[i].TrimmedName; + if (_caseInsensitiveLookup.TryGetValue(parameterName, out var currentI) && currentI + 1 == i) + _caseInsensitiveLookup[parameterName] = i; + } + + _caseInsensitiveLookup[name] = index; + } + } + + void LookupRemove(string name, int index) + { + if (_caseInsensitiveLookup is null) + return; + + if (TwoPassCompatMode && _caseSensitiveLookup!.Remove(name)) + { + for (var i = index; i < InternalList.Count; i++) + { + var parameterName = InternalList[i].TrimmedName; + if (_caseSensitiveLookup.TryGetValue(parameterName, out var currentI) && currentI - 1 == i) + _caseSensitiveLookup[parameterName] = i; + } + } + + if (_caseInsensitiveLookup.Remove(name)) + { + for (var i = index; i < InternalList.Count; i++) + { + var parameterName = InternalList[i].TrimmedName; + if (_caseInsensitiveLookup.TryGetValue(parameterName, out var currentI) && currentI - 1 == i) + _caseInsensitiveLookup[parameterName] = i; + } + + // Fix-up the case-insensitive lookup to point to the next match, if any. + for (var i = 0; i < InternalList.Count; i++) + { + var value = InternalList[i]; + if (string.Equals(name, value.TrimmedName, StringComparison.OrdinalIgnoreCase)) + { + _caseInsensitiveLookup[value.TrimmedName] = i; + break; + } + } + } + + } + + void LookupChangeName(NpgsqlParameter parameter, string oldName, string oldTrimmedName, int index) + { + if (string.Equals(oldTrimmedName, parameter.TrimmedName, StringComparison.OrdinalIgnoreCase)) + return; + + if (oldName.Length != 0) + LookupRemove(oldTrimmedName, index); + if (!parameter.IsPositional) + LookupAdd(parameter.TrimmedName, index); + } + + internal void ChangeParameterName(NpgsqlParameter parameter, string? value) + { + var oldName = parameter.ParameterName; + var oldTrimmedName = parameter.TrimmedName; + parameter.ChangeParameterName(value); + + if (_caseInsensitiveLookup is null || _caseInsensitiveLookup.Count == 0) + return; + + var index = IndexOf(parameter); + if (index == -1) // This would be weird. + return; + + LookupChangeName(parameter, oldName, oldTrimmedName, index); + } + + #region NpgsqlParameterCollection Member + + /// + /// Gets the with the specified name. + /// + /// The name of the to retrieve. + /// + /// The with the specified name, or a reference if the parameter is not found. + /// + public new NpgsqlParameter this[string parameterName] + { + get + { + if (parameterName is null) + throw new ArgumentNullException(nameof(parameterName)); + + var index = IndexOf(parameterName); + if (index == -1) + throw new ArgumentException("Parameter not found"); + + return InternalList[index]; + } + set + { + if (parameterName is null) + throw new ArgumentNullException(nameof(parameterName)); + if (value is null) + throw new ArgumentNullException(nameof(value)); + + var index = IndexOf(parameterName); + if (index == -1) + throw new ArgumentException("Parameter not found"); + + if (!string.Equals(parameterName, value.TrimmedName, StringComparison.OrdinalIgnoreCase)) + throw new ArgumentException( + "Parameter name must be a case-insensitive match with the property 'ParameterName' on the given NpgsqlParameter", + nameof(parameterName)); + + var oldValue = InternalList[index]; + LookupChangeName(value, oldValue.ParameterName, oldValue.TrimmedName, index); + + InternalList[index] = value; + } + } + + /// + /// Gets the at the specified index. + /// + /// The zero-based index of the to retrieve. + /// The at the specified index. + public new NpgsqlParameter this[int index] + { + get => InternalList[index]; + set + { + if (value is null) + throw new ArgumentNullException(nameof(value)); + if (value.Collection is not null) + throw new InvalidOperationException("The parameter already belongs to a collection"); + + var oldValue = InternalList[index]; + + if (ReferenceEquals(oldValue, value)) + return; + + LookupChangeName(value, oldValue.ParameterName, oldValue.TrimmedName, index); + + InternalList[index] = value; + value.Collection = this; + oldValue.Collection = null; + } + } + + /// + /// Adds the specified object to the . + /// + /// The to add to the collection. + /// The index of the new object. + public NpgsqlParameter Add(NpgsqlParameter value) + { + if (value is null) + throw new ArgumentNullException(nameof(value)); + if (value.Collection is not null) + throw new InvalidOperationException("The parameter already belongs to a collection"); + + InternalList.Add(value); + value.Collection = this; + if (!value.IsPositional) + LookupAdd(value.TrimmedName, InternalList.Count - 1); + return value; + } + + /// + void ICollection.Add(NpgsqlParameter item) + => Add(item); + + /// + /// Adds a to the given the specified parameter name and + /// value. + /// + /// The name of the . + /// The value of the to add to the collection. + /// The parameter that was added. + public NpgsqlParameter AddWithValue(string parameterName, object value) + => Add(new NpgsqlParameter(parameterName, value)); + + /// + /// Adds a to the given the specified parameter name, + /// data type and value. + /// + /// The name of the . + /// One of the NpgsqlDbType values. + /// The value of the to add to the collection. + /// The parameter that was added. + public NpgsqlParameter AddWithValue(string parameterName, NpgsqlDbType parameterType, object value) + => Add(new NpgsqlParameter(parameterName, parameterType) { Value = value }); + + /// + /// Adds a to the given the specified parameter name and + /// value. + /// + /// The name of the . + /// The value of the to add to the collection. + /// One of the values. + /// The length of the column. + /// The parameter that was added. + public NpgsqlParameter AddWithValue(string parameterName, NpgsqlDbType parameterType, int size, object value) + => Add(new NpgsqlParameter(parameterName, parameterType, size) { Value = value }); + + /// + /// Adds a to the given the specified parameter name and + /// value. + /// + /// The name of the . + /// The value of the to add to the collection. + /// One of the values. + /// The length of the column. + /// The name of the source column. + /// The parameter that was added. + public NpgsqlParameter AddWithValue(string parameterName, NpgsqlDbType parameterType, int size, string? sourceColumn, object value) + => Add(new NpgsqlParameter(parameterName, parameterType, size, sourceColumn) { Value = value }); + + /// + /// Adds a to the given the specified value. + /// + /// The value of the to add to the collection. + /// The parameter that was added. + public NpgsqlParameter AddWithValue(object value) + => Add(new NpgsqlParameter { Value = value }); + + /// + /// Adds a to the given the specified data type and value. + /// + /// One of the values. + /// The value of the to add to the collection. + /// The parameter that was added. + public NpgsqlParameter AddWithValue(NpgsqlDbType parameterType, object value) + => Add(new NpgsqlParameter { NpgsqlDbType = parameterType, Value = value }); + + /// + /// Adds a to the given the parameter name and the data type. + /// + /// The name of the parameter. + /// One of the values. + /// The index of the new object. + public NpgsqlParameter Add(string parameterName, NpgsqlDbType parameterType) + => Add(new NpgsqlParameter(parameterName, parameterType)); + + /// + /// Adds a to the with the parameter name, the data type, + /// and the column length. + /// + /// The name of the parameter. + /// One of the values. + /// The length of the column. + /// The index of the new object. + public NpgsqlParameter Add(string parameterName, NpgsqlDbType parameterType, int size) + => Add(new NpgsqlParameter(parameterName, parameterType, size)); + + /// + /// Adds a to the with the parameter name, the data type, the + /// column length, and the source column name. + /// + /// The name of the parameter. + /// One of the values. + /// The length of the column. + /// The name of the source column. + /// The index of the new object. + public NpgsqlParameter Add(string parameterName, NpgsqlDbType parameterType, int size, string sourceColumn) + => Add(new NpgsqlParameter(parameterName, parameterType, size, sourceColumn)); + + #endregion + + #region IDataParameterCollection Member + + /// + // ReSharper disable once ImplicitNotNullOverridesUnknownExternalMember + public override void RemoveAt(string parameterName) + => RemoveAt(IndexOf(parameterName ?? throw new ArgumentNullException(nameof(parameterName)))); + + /// + public override bool Contains(string parameterName) + => IndexOf(parameterName ?? throw new ArgumentNullException(nameof(parameterName))) != -1; + + /// + public override int IndexOf(string parameterName) + { + if (parameterName is null) + return -1; + + if (parameterName.Length > 0 && (parameterName[0] == ':' || parameterName[0] == '@')) + parameterName = parameterName.Remove(0, 1); + + // Using a dictionary is always faster after around 10 items when matched against reference equality. + // For string equality this is the case after ~3 items so we take a decent compromise going with 5. + if (LookupEnabled && parameterName.Length != 0) + { + if (_caseInsensitiveLookup is null) + BuildLookup(); + + if (TwoPassCompatMode && _caseSensitiveLookup!.TryGetValue(parameterName, out var indexCs)) + return indexCs; + + if (_caseInsensitiveLookup!.TryGetValue(parameterName, out var indexCi)) + return indexCi; + + return -1; + } + + // Start with case-sensitive search in two pass mode. + if (TwoPassCompatMode) + { + for (var i = 0; i < InternalList.Count; i++) + { + var name = InternalList[i].TrimmedName; + if (string.Equals(parameterName, InternalList[i].TrimmedName)) + return i; + } + } + + // Then do case-insensitive search. + for (var i = 0; i < InternalList.Count; i++) + { + var name = InternalList[i].TrimmedName; + if (ReferenceEquals(parameterName, name) || string.Equals(parameterName, name, StringComparison.OrdinalIgnoreCase)) + return i; + } + + return -1; + + void BuildLookup() + { + if (TwoPassCompatMode) + _caseSensitiveLookup = new Dictionary(InternalList.Count, StringComparer.Ordinal); + + _caseInsensitiveLookup = new Dictionary(InternalList.Count, StringComparer.OrdinalIgnoreCase); + + for (var i = 0; i < InternalList.Count; i++) + { + var item = InternalList[i]; + if (!item.IsPositional) + LookupAdd(item.TrimmedName, i); + } + } + } + + #endregion + + #region IList Member + + /// + public override bool IsReadOnly => false; + + /// + /// Removes the specified from the collection using a specific index. + /// + /// The zero-based index of the parameter. + public override void RemoveAt(int index) + { + if (InternalList.Count - 1 < index) + throw new ArgumentOutOfRangeException(nameof(index)); + + Remove(InternalList[index]); + } + + /// + public override void Insert(int index, object value) + => Insert(index, Cast(value)); + + /// + /// Removes the specified from the collection. + /// + /// The name of the to remove from the collection. + public void Remove(string parameterName) + { + if (parameterName is null) + throw new ArgumentNullException(nameof(parameterName)); + + var index = IndexOf(parameterName); + if (index < 0) + throw new InvalidOperationException("No parameter with the specified name exists in the collection"); + + RemoveAt(index); + } + + /// + /// Removes the specified from the collection. + /// + /// The to remove from the collection. + public override void Remove(object value) + => Remove(Cast(value)); + + /// + public override bool Contains(object value) + => value is NpgsqlParameter param && InternalList.Contains(param); + + /// + /// Gets a value indicating whether a with the specified parameter name exists in the collection. + /// + /// The name of the object to find. + /// + /// A reference to the requested parameter is returned in this out param if it is found in the list. + /// This value is if the parameter is not found. + /// + /// + /// if the collection contains the parameter and param will contain the parameter; + /// otherwise, . + /// + public bool TryGetValue(string parameterName, [NotNullWhen(true)] out NpgsqlParameter? parameter) + { + if (parameterName is null) + throw new ArgumentNullException(nameof(parameterName)); + + var index = IndexOf(parameterName); + + if (index != -1) + { + parameter = InternalList[index]; + return true; + } + + parameter = null; + return false; + } + + /// + /// Removes all items from the collection. + /// + public override void Clear() + { + // clean up parameters so they can be added to another command if required. + foreach (var toRemove in InternalList) + toRemove.Collection = null; + + InternalList.Clear(); + LookupClear(); + } + + /// + public override int IndexOf(object value) + => IndexOf(Cast(value)); + + /// + public override int Add(object value) + { + Add(Cast(value)); + return Count - 1; + } + + /// + public override bool IsFixedSize => false; + + #endregion + + #region ICollection Member + + /// + public override bool IsSynchronized => (InternalList as ICollection).IsSynchronized; + + /// + /// Gets the number of objects in the collection. + /// + /// The number of objects in the collection. + public override int Count => InternalList.Count; + + /// + public override void CopyTo(Array array, int index) + => ((ICollection)InternalList).CopyTo(array, index); + + /// + bool ICollection.IsReadOnly => false; + + /// + public override object SyncRoot => ((ICollection)InternalList).SyncRoot; + + #endregion + + #region IEnumerable Member + + IEnumerator IEnumerable.GetEnumerator() + => InternalList.GetEnumerator(); + + /// + public override IEnumerator GetEnumerator() => InternalList.GetEnumerator(); + + #endregion + + /// + public override void AddRange(Array values) + { + if (values is null) + throw new ArgumentNullException(nameof(values)); + + foreach (var parameter in values) + Add(Cast(parameter) ?? throw new ArgumentException("Collection contains a null value.", nameof(values))); + } + + /// + protected override DbParameter GetParameter(string parameterName) + => this[parameterName]; + + /// + protected override DbParameter GetParameter(int index) + => this[index]; + + /// + protected override void SetParameter(string parameterName, DbParameter value) + => this[parameterName] = Cast(value); + + /// + protected override void SetParameter(int index, DbParameter value) + => this[index] = Cast(value); + + /// + /// Report the offset within the collection of the given parameter. + /// + /// Parameter to find. + /// Index of the parameter, or -1 if the parameter is not present. + public int IndexOf(NpgsqlParameter item) + => InternalList.IndexOf(item); + + /// + /// Insert the specified parameter into the collection. + /// + /// Index of the existing parameter before which to insert the new one. + /// Parameter to insert. + public void Insert(int index, NpgsqlParameter item) + { + if (item is null) + throw new ArgumentNullException(nameof(item)); + if (item.Collection != null) + throw new Exception("The parameter already belongs to a collection"); + + InternalList.Insert(index, item); + item.Collection = this; + if (!item.IsPositional) + LookupInsert(item.TrimmedName, index); + } + + /// + /// Report whether the specified parameter is present in the collection. + /// + /// Parameter to find. + /// True if the parameter was found, otherwise false. + public bool Contains(NpgsqlParameter item) => InternalList.Contains(item); + + /// + /// Remove the specified parameter from the collection. + /// + /// Parameter to remove. + /// True if the parameter was found and removed, otherwise false. + public bool Remove(NpgsqlParameter item) + { + if (item == null) + throw new ArgumentNullException(nameof(item)); + if (item.Collection != this) + throw new InvalidOperationException("The item does not belong to this collection"); + + var index = IndexOf(item); + if (index >= 0) + { + InternalList.RemoveAt(index); + if (!LookupEnabled) + LookupClear(); + if (!item.IsPositional) + LookupRemove(item.TrimmedName, index); + item.Collection = null; + return true; + } + + return false; + } + + /// + /// Convert collection to a System.Array. + /// + /// Destination array. + /// Starting index in destination array. + public void CopyTo(NpgsqlParameter[] array, int arrayIndex) + => InternalList.CopyTo(array, arrayIndex); + + /// + /// Convert collection to a System.Array. + /// + /// NpgsqlParameter[] + public NpgsqlParameter[] ToArray() => InternalList.ToArray(); + + internal void CloneTo(NpgsqlParameterCollection other) + { + other.InternalList.Clear(); + foreach (var param in InternalList) + { + var newParam = param.Clone(); + newParam.Collection = this; + other.InternalList.Add(newParam); + } + + if (LookupEnabled && _caseInsensitiveLookup is not null) + { + other._caseInsensitiveLookup = new Dictionary(_caseInsensitiveLookup, StringComparer.OrdinalIgnoreCase); + if (TwoPassCompatMode) + { + Debug.Assert(_caseSensitiveLookup is not null); + other._caseSensitiveLookup = new Dictionary(_caseSensitiveLookup, StringComparer.Ordinal); + } + } + } + + internal void ValidateAndBind(ConnectorTypeMapper typeMapper) + { + HasOutputParameters = false; + PlaceholderType = PlaceholderType.NoParameters; + + for (var i = 0; i < InternalList.Count; i++) + { + var p = InternalList[i]; + + CalculatePlaceholderType(p); + + switch (p.Direction) + { + case ParameterDirection.Input: + break; + + case ParameterDirection.InputOutput: + if (PlaceholderType == PlaceholderType.Positional) + throw new NotSupportedException("Output parameters are not supported in positional mode"); + HasOutputParameters = true; + break; + + case ParameterDirection.Output: + if (PlaceholderType == PlaceholderType.Positional) + throw new NotSupportedException("Output parameters are not supported in positional mode"); + HasOutputParameters = true; + continue; + + case ParameterDirection.ReturnValue: + // Simply ignored + continue; + + default: + throw new ArgumentOutOfRangeException(nameof(ParameterDirection), + $"Unhandled {nameof(ParameterDirection)} value: {p.Direction}"); + } + + p.Bind(typeMapper); + p.LengthCache?.Clear(); + p.ValidateAndGetLength(); + } + } + + internal void CalculatePlaceholderType(NpgsqlParameter p) + { + if (p.IsPositional) + { + switch (PlaceholderType) + { + case PlaceholderType.NoParameters: + PlaceholderType = PlaceholderType.Positional; + break; + + case PlaceholderType.Named: + PlaceholderType = PlaceholderType.Mixed; + break; + + case PlaceholderType.Positional: + case PlaceholderType.Mixed: + break; + + default: + throw new ArgumentOutOfRangeException( + nameof(PlaceholderType), $"Unknown {nameof(PlaceholderType)} value: {PlaceholderType}"); + } + } + else + { + switch (PlaceholderType) + { + case PlaceholderType.NoParameters: + PlaceholderType = PlaceholderType.Named; + break; + + case PlaceholderType.Positional: + PlaceholderType = PlaceholderType.Mixed; + break; + + case PlaceholderType.Named: + case PlaceholderType.Mixed: + break; + + default: + throw new ArgumentOutOfRangeException( + nameof(PlaceholderType), $"Unknown {nameof(PlaceholderType)} value: {PlaceholderType}"); + } + } + } + + internal bool HasOutputParameters { get; set; } + internal PlaceholderType PlaceholderType { get; set; } + + static NpgsqlParameter Cast(object? value) + => value is NpgsqlParameter p + ? p + : throw new InvalidCastException( + $"The value \"{value}\" is not of type \"{nameof(NpgsqlParameter)}\" and cannot be used in this parameter collection."); +} + +enum PlaceholderType +{ + /// + /// The parameter collection includes no parameters. + /// + NoParameters, + + /// + /// The parameter collection includes only named parameters. + /// + Named, + + /// + /// The parameter collection includes only positional parameters. + /// + Positional, + + /// + /// The parameter collection includes both named and positional parameters. + /// This is only supported when is set to . + /// + Mixed +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlParameter`.cs b/LibExternal/Npgsql/NpgsqlParameter`.cs new file mode 100644 index 0000000..112b841 --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlParameter`.cs @@ -0,0 +1,116 @@ +using System; +using System.Data; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.Internal; +using Npgsql.TypeMapping; +using NpgsqlTypes; +using static Npgsql.Util.Statics; + +namespace Npgsql; + +/// +/// A generic version of which provides more type safety and +/// avoids boxing of value types. Use instead of . +/// +/// The type of the value that will be stored in the parameter. +public sealed class NpgsqlParameter : NpgsqlParameter +{ + /// + /// Gets or sets the strongly-typed value of the parameter. + /// + public T? TypedValue { get; set; } + + /// + /// Gets or sets the value of the parameter. This delegates to . + /// + public override object? Value + { + get => TypedValue; + set => TypedValue = (T)value!; + } + + #region Constructors + + /// + /// Initializes a new instance of . + /// + public NpgsqlParameter() {} + + /// + /// Initializes a new instance of with a parameter name and value. + /// + public NpgsqlParameter(string parameterName, T value) + { + ParameterName = parameterName; + TypedValue = value; + } + + /// + /// Initializes a new instance of with a parameter name and type. + /// + public NpgsqlParameter(string parameterName, NpgsqlDbType npgsqlDbType) + { + ParameterName = parameterName; + NpgsqlDbType = npgsqlDbType; + } + + /// + /// Initializes a new instance of with a parameter name and type. + /// + public NpgsqlParameter(string parameterName, DbType dbType) + { + ParameterName = parameterName; + DbType = dbType; + } + + #endregion Constructors + + internal override void ResolveHandler(ConnectorTypeMapper typeMapper) + { + if (Handler is not null) + return; + + // TODO: Better exceptions in case of cast failure etc. + if (_npgsqlDbType.HasValue) + Handler = typeMapper.ResolveByNpgsqlDbType(_npgsqlDbType.Value); + else if (_dataTypeName is not null) + Handler = typeMapper.ResolveByDataTypeName(_dataTypeName); + else + Handler = typeMapper.ResolveByValue(TypedValue); + } + + internal override int ValidateAndGetLength() + { + if (TypedValue is null or DBNull) + return 0; + + var lengthCache = LengthCache; + var len = Handler!.ValidateAndGetLength(TypedValue, ref lengthCache, this); + LengthCache = lengthCache; + return len; + } + + internal override Task WriteWithLength(NpgsqlWriteBuffer buf, bool async, CancellationToken cancellationToken = default) + => Handler!.WriteWithLength(TypedValue, buf, LengthCache, this, async, cancellationToken); + + private protected override NpgsqlParameter CloneCore() => + // use fields instead of properties + // to avoid auto-initializing something like type_info + new NpgsqlParameter + { + _precision = _precision, + _scale = _scale, + _size = _size, + _npgsqlDbType = _npgsqlDbType, + _dataTypeName = _dataTypeName, + Direction = Direction, + IsNullable = IsNullable, + _name = _name, + TrimmedName = TrimmedName, + SourceColumn = SourceColumn, + SourceVersion = SourceVersion, + TypedValue = TypedValue, + SourceColumnNullMapping = SourceColumnNullMapping, + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlRawCopyStream.cs b/LibExternal/Npgsql/NpgsqlRawCopyStream.cs new file mode 100644 index 0000000..bafaa28 --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlRawCopyStream.cs @@ -0,0 +1,567 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.Internal; +using Npgsql.Logging; +using static Npgsql.Util.Statics; + +#pragma warning disable 1591 + +namespace Npgsql; + +/// +/// Provides an API for a raw binary COPY operation, a high-performance data import/export mechanism to +/// a PostgreSQL table. Initiated by +/// +/// +/// See https://www.postgresql.org/docs/current/static/sql-copy.html. +/// +public sealed class NpgsqlRawCopyStream : Stream, ICancelable +{ + #region Fields and Properties + + NpgsqlConnector _connector; + NpgsqlReadBuffer _readBuf; + NpgsqlWriteBuffer _writeBuf; + + int _leftToReadInDataMsg; + bool _isDisposed, _isConsumed; + + bool _canRead; + bool _canWrite; + + internal bool IsBinary { get; private set; } + + public override bool CanWrite => _canWrite; + public override bool CanRead => _canRead; + + public override bool CanTimeout => true; + public override int WriteTimeout + { + get => (int) _writeBuf.Timeout.TotalMilliseconds; + set => _writeBuf.Timeout = TimeSpan.FromMilliseconds(value); + } + public override int ReadTimeout + { + get => (int) _readBuf.Timeout.TotalMilliseconds; + set + { + _readBuf.Timeout = TimeSpan.FromMilliseconds(value); + // While calling the connector it will overwrite our read buffer timeout + _connector.UserTimeout = value; + } + } + + /// + /// The copy binary format header signature + /// + internal static readonly byte[] BinarySignature = + { + (byte)'P',(byte)'G',(byte)'C',(byte)'O',(byte)'P',(byte)'Y', + (byte)'\n', 255, (byte)'\r', (byte)'\n', 0 + }; + + static readonly NpgsqlLogger Log = NpgsqlLogManager.CreateLogger(nameof(NpgsqlRawCopyStream)); + + #endregion + + #region Constructor / Initializer + + internal NpgsqlRawCopyStream(NpgsqlConnector connector) + { + _connector = connector; + _readBuf = connector.ReadBuffer; + _writeBuf = connector.WriteBuffer; + } + + internal async Task Init(string copyCommand, bool async, CancellationToken cancellationToken = default) + { + await _connector.WriteQuery(copyCommand, async, cancellationToken); + await _connector.Flush(async, cancellationToken); + + using var registration = _connector.StartNestedCancellableOperation(cancellationToken, attemptPgCancellation: false); + + var msg = await _connector.ReadMessage(async); + switch (msg.Code) + { + case BackendMessageCode.CopyInResponse: + var copyInResponse = (CopyInResponseMessage) msg; + IsBinary = copyInResponse.IsBinary; + _canWrite = true; + _writeBuf.StartCopyMode(); + break; + case BackendMessageCode.CopyOutResponse: + var copyOutResponse = (CopyOutResponseMessage) msg; + IsBinary = copyOutResponse.IsBinary; + _canRead = true; + break; + case BackendMessageCode.CommandComplete: + throw new InvalidOperationException( + "This API only supports import/export from the client, i.e. COPY commands containing TO/FROM STDIN. " + + "To import/export with files on your PostgreSQL machine, simply execute the command with ExecuteNonQuery. " + + "Note that your data has been successfully imported/exported."); + default: + throw _connector.UnexpectedMessageReceived(msg.Code); + } + } + + #endregion + + #region Write + + public override void Write(byte[] buffer, int offset, int count) + { + ValidateArguments(buffer, offset, count); + Write(new ReadOnlySpan(buffer, offset, count)); + } + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + ValidateArguments(buffer, offset, count); + return WriteAsync(new Memory(buffer, offset, count), cancellationToken).AsTask(); + } + +#if NETSTANDARD2_0 + public void Write(ReadOnlySpan buffer) +#else + public override void Write(ReadOnlySpan buffer) +#endif + { + CheckDisposed(); + if (!CanWrite) + throw new InvalidOperationException("Stream not open for writing"); + + if (buffer.Length == 0) { return; } + + if (buffer.Length <= _writeBuf.WriteSpaceLeft) + { + _writeBuf.WriteBytes(buffer); + return; + } + + // Value is too big, flush. + Flush(); + + if (buffer.Length <= _writeBuf.WriteSpaceLeft) + { + _writeBuf.WriteBytes(buffer); + return; + } + + // Value is too big even after a flush - bypass the buffer and write directly. + _writeBuf.DirectWrite(buffer); + } + +#if NETSTANDARD2_0 + public ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) +#else + public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) +#endif + { + CheckDisposed(); + if (!CanWrite) + throw new InvalidOperationException("Stream not open for writing"); + cancellationToken.ThrowIfCancellationRequested(); + using (NoSynchronizationContextScope.Enter()) + return WriteAsyncInternal(buffer, cancellationToken); + + async ValueTask WriteAsyncInternal(ReadOnlyMemory buffer, CancellationToken cancellationToken) + { + if (buffer.Length == 0) + return; + + if (buffer.Length <= _writeBuf.WriteSpaceLeft) + { + _writeBuf.WriteBytes(buffer.Span); + return; + } + + // Value is too big, flush. + await FlushAsync(true, cancellationToken); + + if (buffer.Length <= _writeBuf.WriteSpaceLeft) + { + _writeBuf.WriteBytes(buffer.Span); + return; + } + + // Value is too big even after a flush - bypass the buffer and write directly. + await _writeBuf.DirectWrite(buffer, true, cancellationToken); + } + } + + public override void Flush() => FlushAsync(false).GetAwaiter().GetResult(); + + public override Task FlushAsync(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + using (NoSynchronizationContextScope.Enter()) + return FlushAsync(true, cancellationToken); + } + + Task FlushAsync(bool async, CancellationToken cancellationToken = default) + { + CheckDisposed(); + return _writeBuf.Flush(async, cancellationToken); + } + + #endregion + + #region Read + + public override int Read(byte[] buffer, int offset, int count) + { + ValidateArguments(buffer, offset, count); + return Read(new Span(buffer, offset, count)); + } + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + ValidateArguments(buffer, offset, count); + return ReadAsync(new Memory(buffer, offset, count), cancellationToken).AsTask(); + } + +#if NETSTANDARD2_0 + public int Read(Span span) +#else + public override int Read(Span span) +#endif + { + CheckDisposed(); + if (!CanRead) + throw new InvalidOperationException("Stream not open for reading"); + + var count = ReadCore(span.Length, false).GetAwaiter().GetResult(); + if (count > 0) + _readBuf.ReadBytes(span.Slice(0, count)); + return count; + } + +#if NETSTANDARD2_0 + public ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken) +#else + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken) +#endif + { + CheckDisposed(); + if (!CanRead) + throw new InvalidOperationException("Stream not open for reading"); + cancellationToken.ThrowIfCancellationRequested(); + using (NoSynchronizationContextScope.Enter()) + return ReadAsyncInternal(); + + async ValueTask ReadAsyncInternal() + { + var count = await ReadCore(buffer.Length, true, cancellationToken); + if (count > 0) + _readBuf.ReadBytes(buffer.Slice(0, count).Span); + return count; + } + } + + async ValueTask ReadCore(int count, bool async, CancellationToken cancellationToken = default) + { + if (_isConsumed) + return 0; + + using var registration = _connector.StartNestedCancellableOperation(cancellationToken, attemptPgCancellation: false); + + if (_leftToReadInDataMsg == 0) + { + IBackendMessage msg; + try + { + // We've consumed the current DataMessage (or haven't yet received the first), + // read the next message + msg = await _connector.ReadMessage(async); + } + catch + { + if (!_isDisposed) + Cleanup(); + throw; + } + + switch (msg.Code) + { + case BackendMessageCode.CopyData: + _leftToReadInDataMsg = ((CopyDataMessage)msg).Length; + break; + case BackendMessageCode.CopyDone: + Expect(await _connector.ReadMessage(async), _connector); + Expect(await _connector.ReadMessage(async), _connector); + _isConsumed = true; + return 0; + default: + throw _connector.UnexpectedMessageReceived(msg.Code); + } + } + + Debug.Assert(_leftToReadInDataMsg > 0); + + // If our buffer is empty, read in more. Otherwise return whatever is there, even if the + // user asked for more (normal socket behavior) + if (_readBuf.ReadBytesLeft == 0) + await _readBuf.ReadMore(async); + + Debug.Assert(_readBuf.ReadBytesLeft > 0); + + var maxCount = Math.Min(_readBuf.ReadBytesLeft, _leftToReadInDataMsg); + if (count > maxCount) + count = maxCount; + + _leftToReadInDataMsg -= count; + return count; + } + + #endregion + + #region Cancel + + /// + /// Cancels and terminates an ongoing operation. Any data already written will be discarded. + /// + public void Cancel() => Cancel(false).GetAwaiter().GetResult(); + + /// + /// Cancels and terminates an ongoing operation. Any data already written will be discarded. + /// + public Task CancelAsync() + { + using (NoSynchronizationContextScope.Enter()) + return Cancel(true); + } + + async Task Cancel(bool async) + { + CheckDisposed(); + + if (CanWrite) + { + _writeBuf.EndCopyMode(); + _writeBuf.Clear(); + await _connector.WriteCopyFail(async); + await _connector.Flush(async); + try + { + var msg = await _connector.ReadMessage(async); + // The CopyFail should immediately trigger an exception from the read above. + throw _connector.Break( + new NpgsqlException("Expected ErrorResponse when cancelling COPY but got: " + msg.Code)); + } + catch (PostgresException e) + { + _connector.EndUserAction(); + Cleanup(); + + if (e.SqlState != PostgresErrorCodes.QueryCanceled) + throw; + } + } + else + { + _connector.PerformPostgresCancellation(); + } + } + + #endregion + + #region Dispose + + protected override void Dispose(bool disposing) => DisposeAsync(disposing, false).GetAwaiter().GetResult(); + +#if NETSTANDARD2_0 + public ValueTask DisposeAsync() +#else + public override ValueTask DisposeAsync() +#endif + => DisposeAsync(disposing: true, async: true); + + + async ValueTask DisposeAsync(bool disposing, bool async) + { + if (_isDisposed || !disposing) + return; + + try + { + _connector.CurrentCopyOperation = null; + + if (CanWrite) + { + await FlushAsync(async); + _writeBuf.EndCopyMode(); + await _connector.WriteCopyDone(async); + await _connector.Flush(async); + Expect(await _connector.ReadMessage(async), _connector); + Expect(await _connector.ReadMessage(async), _connector); + } + else + { + if (!_isConsumed) + { + try + { + if (_leftToReadInDataMsg > 0) + { + await _readBuf.Skip(_leftToReadInDataMsg, async); + } + _connector.SkipUntil(BackendMessageCode.ReadyForQuery); + } + catch (OperationCanceledException e) when (e.InnerException is PostgresException pg && pg.SqlState == PostgresErrorCodes.QueryCanceled) + { + Log.Debug($"Caught an exception while disposing the {nameof(NpgsqlRawCopyStream)}, indicating that it was cancelled.", e, _connector.Id); + } + catch (Exception e) + { + Log.Error($"Caught an exception while disposing the {nameof(NpgsqlRawCopyStream)}.", e, _connector.Id); + } + } + } + } + finally + { + _connector.EndUserAction(); + Cleanup(); + } + } + +#pragma warning disable CS8625 + void Cleanup() + { + Debug.Assert(!_isDisposed); + Log.Debug("COPY operation ended", _connector.Id); + _connector.CurrentCopyOperation = null; + _connector.Connection?.EndBindingScope(ConnectorBindingScope.Copy); + _connector = null; + _readBuf = null; + _writeBuf = null; + _isDisposed = true; + } +#pragma warning restore CS8625 + + void CheckDisposed() + { + if (_isDisposed) { + throw new ObjectDisposedException(nameof(NpgsqlRawCopyStream), "The COPY operation has already ended."); + } + } + + #endregion + + #region Unsupported + + public override bool CanSeek => false; + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + #endregion + + #region Input validation + static void ValidateArguments(byte[] buffer, int offset, int count) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + if (offset < 0) + throw new ArgumentNullException(nameof(offset)); + if (count < 0) + throw new ArgumentNullException(nameof(count)); + if (buffer.Length - offset < count) + throw new ArgumentException("Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection."); + } + #endregion +} + +/// +/// Writer for a text import, initiated by . +/// +/// +/// See https://www.postgresql.org/docs/current/static/sql-copy.html. +/// +public sealed class NpgsqlCopyTextWriter : StreamWriter, ICancelable +{ + internal NpgsqlCopyTextWriter(NpgsqlConnector connector, NpgsqlRawCopyStream underlying) : base(underlying) + { + if (underlying.IsBinary) + throw connector.Break(new Exception("Can't use a binary copy stream for text writing")); + } + + /// + /// Cancels and terminates an ongoing import. Any data already written will be discarded. + /// + public void Cancel() + => ((NpgsqlRawCopyStream)BaseStream).Cancel(); + + /// + /// Cancels and terminates an ongoing import. Any data already written will be discarded. + /// + public Task CancelAsync() + { + using (NoSynchronizationContextScope.Enter()) + return ((NpgsqlRawCopyStream)BaseStream).CancelAsync(); + } + +#if NETSTANDARD2_0 + public ValueTask DisposeAsync() + { + Dispose(); + return default; + } +#endif +} + +/// +/// Reader for a text export, initiated by . +/// +/// +/// See https://www.postgresql.org/docs/current/static/sql-copy.html. +/// +public sealed class NpgsqlCopyTextReader : StreamReader, ICancelable +{ + internal NpgsqlCopyTextReader(NpgsqlConnector connector, NpgsqlRawCopyStream underlying) : base(underlying) + { + if (underlying.IsBinary) + throw connector.Break(new Exception("Can't use a binary copy stream for text reading")); + } + + /// + /// Cancels and terminates an ongoing export. + /// + public void Cancel() + => ((NpgsqlRawCopyStream)BaseStream).Cancel(); + + /// + /// Asynchronously cancels and terminates an ongoing export. + /// + public Task CancelAsync() + { + using (NoSynchronizationContextScope.Enter()) + return ((NpgsqlRawCopyStream)BaseStream).CancelAsync(); + } + + public ValueTask DisposeAsync() + { + Dispose(); + return default; + } +} diff --git a/LibExternal/Npgsql/NpgsqlSchema.cs b/LibExternal/Npgsql/NpgsqlSchema.cs new file mode 100644 index 0000000..e8d65ec --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlSchema.cs @@ -0,0 +1,871 @@ +using System; +using System.Data; +using System.Data.Common; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.PostgresTypes; +using NpgsqlTypes; + +namespace Npgsql; + +/// +/// Provides the underlying mechanism for reading schema information. +/// +static class NpgsqlSchema +{ + public static Task GetSchema(NpgsqlConnection conn, string? collectionName, string?[]? restrictions, bool async, CancellationToken cancellationToken = default) + { + if (collectionName is null) + throw new ArgumentNullException(nameof(collectionName)); + if (collectionName.Length == 0) + throw new ArgumentException("Collection name cannot be empty.", nameof(collectionName)); + + return collectionName.ToUpperInvariant() switch + { + "METADATACOLLECTIONS" => Task.FromResult(GetMetaDataCollections()), + "RESTRICTIONS" => Task.FromResult(GetRestrictions()), + "DATASOURCEINFORMATION" => Task.FromResult(GetDataSourceInformation(conn)), + "DATATYPES" => Task.FromResult(GetDataTypes(conn)), + "RESERVEDWORDS" => Task.FromResult(GetReservedWords()), + // custom collections for npgsql + "DATABASES" => GetDatabases(conn, restrictions, async, cancellationToken), + "SCHEMATA" => GetSchemata(conn, restrictions, async, cancellationToken), + "TABLES" => GetTables(conn, restrictions, async, cancellationToken), + "COLUMNS" => GetColumns(conn, restrictions, async, cancellationToken), + "VIEWS" => GetViews(conn, restrictions, async, cancellationToken), + "USERS" => GetUsers(conn, restrictions, async, cancellationToken), + "INDEXES" => GetIndexes(conn, restrictions, async, cancellationToken), + "INDEXCOLUMNS" => GetIndexColumns(conn, restrictions, async, cancellationToken), + "CONSTRAINTS" => GetConstraints(conn, restrictions, collectionName, async, cancellationToken), + "PRIMARYKEY" => GetConstraints(conn, restrictions, collectionName, async, cancellationToken), + "UNIQUEKEYS" => GetConstraints(conn, restrictions, collectionName, async, cancellationToken), + "FOREIGNKEYS" => GetConstraints(conn, restrictions, collectionName, async, cancellationToken), + "CONSTRAINTCOLUMNS" => GetConstraintColumns(conn, restrictions, async, cancellationToken), + _ => throw new ArgumentOutOfRangeException(nameof(collectionName), collectionName, "Invalid collection name.") + }; + } + + /// + /// Returns the MetaDataCollections that lists all possible collections. + /// + /// The MetaDataCollections + static DataTable GetMetaDataCollections() + { + var table = new DataTable("MetaDataCollections"); + table.Columns.Add("CollectionName", typeof(string)); + table.Columns.Add("NumberOfRestrictions", typeof(int)); + table.Columns.Add("NumberOfIdentifierParts", typeof(int)); + + table.Rows.Add("MetaDataCollections", 0, 0); + table.Rows.Add("DataSourceInformation", 0, 0); + table.Rows.Add("Restrictions", 0, 0); + table.Rows.Add("DataTypes", 0, 0); // TODO: Support type name restriction + table.Rows.Add("Databases", 1, 1); + table.Rows.Add("Tables", 4, 3); + table.Rows.Add("Columns", 4, 4); + table.Rows.Add("Views", 3, 3); + table.Rows.Add("Users", 1, 1); + table.Rows.Add("Indexes", 4, 4); + table.Rows.Add("IndexColumns", 5, 5); + + return table; + } + + /// + /// Returns the Restrictions that contains the meaning and position of the values in the restrictions array. + /// + /// The Restrictions + static DataTable GetRestrictions() + { + var table = new DataTable("Restrictions"); + + table.Columns.Add("CollectionName", typeof(string)); + table.Columns.Add("RestrictionName", typeof(string)); + table.Columns.Add("RestrictionDefault", typeof(string)); + table.Columns.Add("RestrictionNumber", typeof(int)); + + table.Rows.Add("Database", "Name", "Name", 1); + table.Rows.Add("Tables", "Catalog", "table_catalog", 1); + table.Rows.Add("Tables", "Schema", "schema_catalog", 2); + table.Rows.Add("Tables", "Table", "table_name", 3); + table.Rows.Add("Tables", "TableType", "table_type", 4); + table.Rows.Add("Columns", "Catalog", "table_catalog", 1); + table.Rows.Add("Columns", "Schema", "table_schema", 2); + table.Rows.Add("Columns", "TableName", "table_name", 3); + table.Rows.Add("Columns", "Column", "column_name", 4); + table.Rows.Add("Views", "Catalog", "table_catalog", 1); + table.Rows.Add("Views", "Schema", "table_schema", 2); + table.Rows.Add("Views", "Table", "table_name", 3); + table.Rows.Add("Users", "User", "user_name", 1); + table.Rows.Add("Indexes", "Catalog", "table_catalog", 1); + table.Rows.Add("Indexes", "Schema", "table_schema", 2); + table.Rows.Add("Indexes", "Table", "table_name", 3); + table.Rows.Add("Indexes", "Index", "index_name", 4); + table.Rows.Add("IndexColumns", "Catalog", "table_catalog", 1); + table.Rows.Add("IndexColumns", "Schema", "table_schema", 2); + table.Rows.Add("IndexColumns", "Table", "table_name", 3); + table.Rows.Add("IndexColumns", "Index", "index_name", 4); + table.Rows.Add("IndexColumns", "Column", "column_name", 5); + + return table; + } + + static NpgsqlCommand BuildCommand(NpgsqlConnection conn, StringBuilder query, string?[]? restrictions, params string[]? names) + => BuildCommand(conn, query, restrictions, true, names); + + static NpgsqlCommand BuildCommand(NpgsqlConnection conn, StringBuilder query, string?[]? restrictions, bool addWhere, params string[]? names) + { + var command = new NpgsqlCommand(); + + if (restrictions != null && names != null) + { + for (var i = 0; i < restrictions.Length && i < names.Length; ++i) + { + if (restrictions[i] is string restriction && restriction.Length != 0) + { + if (addWhere) + { + query.Append(" WHERE "); + addWhere = false; + } + else + { + query.Append(" AND "); + } + + var paramName = RemoveSpecialChars(names[i]); + + query.AppendFormat("{0} = :{1}", names[i], paramName); + + command.Parameters.Add(new NpgsqlParameter(paramName, restriction)); + } + } + } + command.CommandText = query.ToString(); + command.Connection = conn; + + return command; + } + + static string RemoveSpecialChars(string paramName) + => paramName.Replace("(", "").Replace(")", "").Replace(".", ""); + + static async Task GetDatabases(NpgsqlConnection conn, string?[]? restrictions, bool async, CancellationToken cancellationToken = default) + { + var databases = new DataTable("Databases") { Locale = CultureInfo.InvariantCulture }; + + databases.Columns.AddRange(new[] { + new DataColumn("database_name"), + new DataColumn("owner"), + new DataColumn("encoding") + }); + + var getDatabases = new StringBuilder(); + + getDatabases.Append("SELECT d.datname AS database_name, u.usename AS owner, pg_catalog.pg_encoding_to_char(d.encoding) AS encoding FROM pg_catalog.pg_database d LEFT JOIN pg_catalog.pg_user u ON d.datdba = u.usesysid"); + + using var command = BuildCommand(conn, getDatabases, restrictions, "datname"); + using var adapter = new NpgsqlDataAdapter(command); + await adapter.Fill(databases, async, cancellationToken); + + return databases; + } + + static async Task GetSchemata(NpgsqlConnection conn, string?[]? restrictions, bool async, CancellationToken cancellationToken = default) + { + var schemata = new DataTable("Schemata") { Locale = CultureInfo.InvariantCulture }; + + schemata.Columns.AddRange(new[] { + new DataColumn("catalog_name"), + new DataColumn("schema_name"), + new DataColumn("schema_owner") + }); + + var getSchemata = new StringBuilder(@" +SELECT * FROM ( + SELECT current_database() AS catalog_name, + nspname AS schema_name, + r.rolname AS schema_owner + FROM + pg_catalog.pg_namespace LEFT JOIN pg_catalog.pg_roles r ON r.oid = nspowner + ) tmp"); + + using var command = BuildCommand(conn, getSchemata, restrictions, "catalog_name", "schema_name", "schema_owner"); + using var adapter = new NpgsqlDataAdapter(command); + await adapter.Fill(schemata, async, cancellationToken); + + return schemata; + } + + + static async Task GetTables(NpgsqlConnection conn, string?[]? restrictions, bool async, CancellationToken cancellationToken = default) + { + var tables = new DataTable("Tables") { Locale = CultureInfo.InvariantCulture }; + + tables.Columns.AddRange(new[] { + new DataColumn("table_catalog"), + new DataColumn("table_schema"), + new DataColumn("table_name"), + new DataColumn("table_type") + }); + + var getTables = new StringBuilder(); + + getTables.Append(@" +SELECT table_catalog, table_schema, table_name, table_type +FROM information_schema.tables +WHERE + table_type IN ('BASE TABLE', 'FOREIGN', 'FOREIGN TABLE') AND + table_schema NOT IN ('pg_catalog', 'information_schema')"); + + using var command = BuildCommand(conn, getTables, restrictions, false, "table_catalog", "table_schema", "table_name", "table_type"); + using var adapter = new NpgsqlDataAdapter(command); + await adapter.Fill(tables, async, cancellationToken); + + return tables; + } + + static async Task GetColumns(NpgsqlConnection conn, string?[]? restrictions, bool async, CancellationToken cancellationToken = default) + { + var columns = new DataTable("Columns") { Locale = CultureInfo.InvariantCulture }; + + columns.Columns.AddRange(new DataColumn[] { + new("table_catalog"), new("table_schema"), new("table_name"), new("column_name"), + new("ordinal_position", typeof(int)), + new("column_default"), + new("is_nullable"), + new("data_type"), + new("character_maximum_length", typeof(int)), new("character_octet_length", typeof(int)), + new("numeric_precision", typeof(int)), new("numeric_precision_radix", typeof(int)), new("numeric_scale", typeof(int)), + new("datetime_precision", typeof(int)), + new("character_set_catalog"), new("character_set_schema"), new("character_set_name"), + new("collation_catalog") + }); + + var getColumns = new StringBuilder(@" +SELECT + table_catalog, table_schema, table_name, column_name, + ordinal_position, + column_default, + is_nullable, + CASE WHEN udt_schema is NULL THEN udt_name ELSE format_type(typ.oid, NULL) END AS data_type, + character_maximum_length, character_octet_length, + numeric_precision, numeric_precision_radix, numeric_scale, + datetime_precision, + character_set_catalog, character_set_schema, character_set_name, + collation_catalog +FROM information_schema.columns +JOIN pg_namespace AS ns ON ns.nspname = udt_schema +JOIN pg_type AS typ ON typnamespace = ns.oid AND typname = udt_name"); + + using var command = BuildCommand(conn, getColumns, restrictions, "table_catalog", "table_schema", "table_name", "column_name"); + using var adapter = new NpgsqlDataAdapter(command); + await adapter.Fill(columns, async, cancellationToken); + + return columns; + } + + static async Task GetViews(NpgsqlConnection conn, string?[]? restrictions, bool async, CancellationToken cancellationToken = default) + { + var views = new DataTable("Views") { Locale = CultureInfo.InvariantCulture }; + + views.Columns.AddRange(new[] { + new DataColumn("table_catalog"), new DataColumn("table_schema"), new DataColumn("table_name"), + new DataColumn("check_option"), new DataColumn("is_updatable") + }); + + var getViews = new StringBuilder(@" +SELECT table_catalog, table_schema, table_name, check_option, is_updatable +FROM information_schema.views +WHERE table_schema NOT IN ('pg_catalog', 'information_schema')"); + + using var command = BuildCommand(conn, getViews, restrictions, false, "table_catalog", "table_schema", "table_name"); + using var adapter = new NpgsqlDataAdapter(command); + await adapter.Fill(views, async, cancellationToken); + + return views; + } + + static async Task GetUsers(NpgsqlConnection conn, string?[]? restrictions, bool async, CancellationToken cancellationToken = default) + { + var users = new DataTable("Users") { Locale = CultureInfo.InvariantCulture }; + + users.Columns.AddRange(new[] { new DataColumn("user_name"), new DataColumn("user_sysid", typeof(uint)) }); + + var getUsers = new StringBuilder(); + + getUsers.Append("SELECT usename as user_name, usesysid as user_sysid FROM pg_catalog.pg_user"); + + using var command = BuildCommand(conn, getUsers, restrictions, "usename"); + using var adapter = new NpgsqlDataAdapter(command); + await adapter.Fill(users, async, cancellationToken); + + return users; + } + + static async Task GetIndexes(NpgsqlConnection conn, string?[]? restrictions, bool async, CancellationToken cancellationToken = default) + { + var indexes = new DataTable("Indexes") { Locale = CultureInfo.InvariantCulture }; + + indexes.Columns.AddRange(new[] { + new DataColumn("table_catalog"), new DataColumn("table_schema"), new DataColumn("table_name"), + new DataColumn("index_name"), new DataColumn("type_desc") + }); + + var getIndexes = new StringBuilder(@" +SELECT current_database() AS table_catalog, + n.nspname AS table_schema, + t.relname AS table_name, + i.relname AS index_name, + '' AS type_desc +FROM + pg_catalog.pg_class i + JOIN pg_catalog.pg_index ix ON ix.indexrelid = i.oid + JOIN pg_catalog.pg_class t ON ix.indrelid = t.oid + LEFT JOIN pg_catalog.pg_user u ON u.usesysid = i.relowner + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = i.relnamespace +WHERE + i.relkind = 'i' AND + n.nspname NOT IN ('pg_catalog', 'pg_toast') AND + t.relkind = 'r'"); + + using var command = BuildCommand(conn, getIndexes, restrictions, false, "current_database()", "n.nspname", "t.relname", "i.relname"); + using var adapter = new NpgsqlDataAdapter(command); + await adapter.Fill(indexes, async, cancellationToken); + + return indexes; + } + + static async Task GetIndexColumns(NpgsqlConnection conn, string?[]? restrictions, bool async, CancellationToken cancellationToken = default) + { + var indexColumns = new DataTable("IndexColumns") { Locale = CultureInfo.InvariantCulture }; + + indexColumns.Columns.AddRange(new[] { + new DataColumn("constraint_catalog"), new DataColumn("constraint_schema"), new DataColumn("constraint_name"), + new DataColumn("table_catalog"), new DataColumn("table_schema"), new DataColumn("table_name"), + new DataColumn("column_name"), new DataColumn("index_name") + }); + + var getIndexColumns = new StringBuilder(@" +SELECT + current_database() AS constraint_catalog, + t_ns.nspname AS constraint_schema, + ix_cls.relname AS constraint_name, + current_database() AS table_catalog, + ix_ns.nspname AS table_schema, + t.relname AS table_name, + a.attname AS column_name, + ix_cls.relname AS index_name +FROM + pg_class t + JOIN pg_index ix ON t.oid = ix.indrelid + JOIN pg_class ix_cls ON ix.indexrelid = ix_cls.oid + JOIN pg_attribute a ON t.oid = a.attrelid + LEFT JOIN pg_namespace t_ns ON t.relnamespace = t_ns.oid + LEFT JOIN pg_namespace ix_ns ON ix_cls.relnamespace = ix_ns.oid +WHERE + ix_cls.relkind = 'i' AND + t_ns.nspname NOT IN ('pg_catalog', 'pg_toast') AND + a.attnum = ANY(ix.indkey) AND + t.relkind = 'r'"); + + using var command = BuildCommand(conn, getIndexColumns, restrictions, false, "current_database()", "t_ns.nspname", "t.relname", "ix_cls.relname", "a.attname"); + using var adapter = new NpgsqlDataAdapter(command); + await adapter.Fill(indexColumns, async, cancellationToken); + + return indexColumns; + } + + static async Task GetConstraints(NpgsqlConnection conn, string?[]? restrictions, string? constraintType, bool async, CancellationToken cancellationToken = default) + { + var getConstraints = new StringBuilder(@" +SELECT + current_database() AS ""CONSTRAINT_CATALOG"", + pgn.nspname AS ""CONSTRAINT_SCHEMA"", + pgc.conname AS ""CONSTRAINT_NAME"", + current_database() AS ""TABLE_CATALOG"", + pgtn.nspname AS ""TABLE_SCHEMA"", + pgt.relname AS ""TABLE_NAME"", + ""CONSTRAINT_TYPE"", + pgc.condeferrable AS ""IS_DEFERRABLE"", + pgc.condeferred AS ""INITIALLY_DEFERRED"" +FROM + pg_catalog.pg_constraint pgc + JOIN pg_catalog.pg_namespace pgn ON pgc.connamespace = pgn.oid + JOIN pg_catalog.pg_class pgt ON pgc.conrelid = pgt.oid + JOIN pg_catalog.pg_namespace pgtn ON pgt.relnamespace = pgtn.oid + JOIN ( + SELECT 'PRIMARY KEY' AS ""CONSTRAINT_TYPE"", 'p' AS ""contype"" + UNION ALL + SELECT 'FOREIGN KEY' AS ""CONSTRAINT_TYPE"", 'f' AS ""contype"" + UNION ALL + SELECT 'UNIQUE KEY' AS ""CONSTRAINT_TYPE"", 'u' AS ""contype"" +) mapping_table ON mapping_table.contype = pgc.contype"); + if ("ForeignKeys".Equals(constraintType)) + getConstraints.Append(" and pgc.contype='f'"); + else if ("PrimaryKey".Equals(constraintType)) + getConstraints.Append(" and pgc.contype='p'"); + else if ("UniqueKeys".Equals(constraintType)) + getConstraints.Append(" and pgc.contype='u'"); + else + constraintType = "Constraints"; + + using var command = BuildCommand(conn, getConstraints, restrictions, false, "current_database()", "pgtn.nspname", "pgt.relname", "pgc.conname"); + using var adapter = new NpgsqlDataAdapter(command); + var table = new DataTable(constraintType) { Locale = CultureInfo.InvariantCulture }; + + await adapter.Fill(table, async, cancellationToken); + + return table; + } + + static async Task GetConstraintColumns(NpgsqlConnection conn, string?[]? restrictions, bool async, CancellationToken cancellationToken = default) + { + var getConstraintColumns = new StringBuilder(@" +SELECT current_database() AS constraint_catalog, + n.nspname AS constraint_schema, + c.conname AS constraint_name, + current_database() AS table_catalog, + n.nspname AS table_schema, + t.relname AS table_name, + a.attname AS column_name, + a.attnum AS ordinal_number, + mapping_table.constraint_type +FROM pg_constraint c + JOIN pg_namespace n on n.oid = c.connamespace + JOIN pg_class t on t.oid = c.conrelid AND t.relkind = 'r' + JOIN pg_attribute a on t.oid = a.attrelid AND a.attnum = ANY(c.conkey) + JOIN ( + SELECT 'PRIMARY KEY' AS constraint_type, 'p' AS contype + UNION ALL + SELECT 'FOREIGN KEY' AS constraint_type, 'f' AS contype + UNION ALL + SELECT 'UNIQUE KEY' AS constraint_type, 'u' AS contype +) mapping_table ON + mapping_table.contype = c.contype + AND n.nspname NOT IN ('pg_catalog', 'pg_toast')"); + + using var command = BuildCommand(conn, getConstraintColumns, restrictions, false, "current_database()", "n.nspname", "t.relname", "c.conname", "a.attname"); + using var adapter = new NpgsqlDataAdapter(command); + var table = new DataTable("ConstraintColumns") { Locale = CultureInfo.InvariantCulture }; + + await adapter.Fill(table, async, cancellationToken); + + return table; + } + + static DataTable GetDataSourceInformation(NpgsqlConnection conn) + { + var table = new DataTable("DataSourceInformation"); + var row = table.Rows.Add(); + + table.Columns.Add("CompositeIdentifierSeparatorPattern", typeof(string)); + // TODO: DefaultCatalog? Was in XML (unfilled) but isn't in docs + table.Columns.Add("DataSourceProductName", typeof(string)); + table.Columns.Add("DataSourceProductVersion", typeof(string)); + table.Columns.Add("DataSourceProductVersionNormalized", typeof(string)); + table.Columns.Add("GroupByBehavior", typeof(GroupByBehavior)); + table.Columns.Add("IdentifierPattern", typeof(string)); + table.Columns.Add("IdentifierCase", typeof(IdentifierCase)); + table.Columns.Add("OrderByColumnsInSelect", typeof(bool)); + table.Columns.Add("ParameterMarkerFormat", typeof(string)); + table.Columns.Add("ParameterMarkerPattern", typeof(string)); + table.Columns.Add("ParameterNameMaxLength", typeof(int)); + table.Columns.Add("QuotedIdentifierPattern", typeof(string)); + table.Columns.Add("QuotedIdentifierCase", typeof(IdentifierCase)); + table.Columns.Add("ParameterNamePattern", typeof(string)); + table.Columns.Add("StatementSeparatorPattern", typeof(string)); + table.Columns.Add("StringLiteralPattern", typeof(string)); + table.Columns.Add("SupportedJoinOperators", typeof(SupportedJoinOperators)); + + var version = conn.PostgreSqlVersion; + var normalizedVersion = $"{version.Major:00}.{version.Minor:00}"; + if (version.Build >= 0) + normalizedVersion += $".{version.Build:00}"; + + row["CompositeIdentifierSeparatorPattern"] = @"\."; + row["DataSourceProductName"] = "Npgsql"; + row["DataSourceProductVersion"] = version.ToString(); + row["DataSourceProductVersionNormalized"] = normalizedVersion; + row["GroupByBehavior"] = GroupByBehavior.Unrelated; + row["IdentifierPattern"] = @"(^\[\p{Lo}\p{Lu}\p{Ll}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Nd}@$#_]*$)|(^\[[^\]\0]|\]\]+\]$)|(^\""[^\""\0]|\""\""+\""$)"; + row["IdentifierCase"] = IdentifierCase.Insensitive; + row["OrderByColumnsInSelect"] = false; + row["QuotedIdentifierPattern"] = @"""(([^\""]|\""\"")*)"""; + row["QuotedIdentifierCase"] = IdentifierCase.Sensitive; + row["StatementSeparatorPattern"] = ";"; + row["StringLiteralPattern"] = @"'(([^']|'')*)'"; + row["SupportedJoinOperators"] = + SupportedJoinOperators.FullOuter | + SupportedJoinOperators.Inner | + SupportedJoinOperators.LeftOuter | + SupportedJoinOperators.RightOuter; + + row["ParameterNameMaxLength"] = 63; // For function out parameters + row["ParameterMarkerFormat"] = @"{0}"; // TODO: Not sure + + if (NpgsqlCommand.EnableSqlRewriting) + { + row["ParameterMarkerPattern"] = @"@[\p{Lo}\p{Lu}\p{Ll}\p{Lm}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Lm}\p{Nd}\uff3f_@#\$]*(?=\s+|$)"; + row["ParameterNamePattern"] = @"^[\p{Lo}\p{Lu}\p{Ll}\p{Lm}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Lm}\p{Nd}\uff3f_@#\$]*(?=\s+|$)"; + } + else + { + row["ParameterMarkerPattern"] = @"$\d+"; + row["ParameterNamePattern"] = @"\d+"; + } + + return table; + } + + #region DataTypes + + static DataTable GetDataTypes(NpgsqlConnection conn) + { + using var _ = conn.StartTemporaryBindingScope(out var connector); + + var table = new DataTable("DataTypes"); + + table.Columns.Add("TypeName", typeof(string)); + table.Columns.Add("ColumnSize", typeof(long)); + table.Columns.Add("CreateFormat", typeof(string)); + table.Columns.Add("CreateParameters", typeof(string)); + table.Columns.Add("DataType", typeof(string)); + table.Columns.Add("IsAutoIncrementable", typeof(bool)); + table.Columns.Add("IsBestMatch", typeof(bool)); + table.Columns.Add("IsCaseSensitive", typeof(bool)); + table.Columns.Add("IsConcurrencyType", typeof(bool)); + table.Columns.Add("IsFixedLength", typeof(bool)); + table.Columns.Add("IsFixedPrecisionAndScale", typeof(bool)); + table.Columns.Add("IsLiteralSupported", typeof(bool)); + table.Columns.Add("IsLong", typeof(bool)); + table.Columns.Add("IsNullable", typeof(bool)); + table.Columns.Add("IsSearchable", typeof(bool)); + table.Columns.Add("IsSearchableWithLike", typeof(bool)); + table.Columns.Add("IsUnsigned", typeof(bool)); + table.Columns.Add("LiteralPrefix", typeof(string)); + table.Columns.Add("LiteralSuffix", typeof(string)); + table.Columns.Add("MaximumScale", typeof(short)); + table.Columns.Add("MinimumScale", typeof(short)); + table.Columns.Add("NativeDataType", typeof(string)); + table.Columns.Add("ProviderDbType", typeof(int)); + + // Npgsql-specific + table.Columns.Add("OID", typeof(uint)); + + // TODO: Support type name restriction + + foreach (var baseType in connector.DatabaseInfo.BaseTypes.Cast() + .Concat(connector.DatabaseInfo.EnumTypes) + .Concat(connector.DatabaseInfo.CompositeTypes)) + { + if (!connector.TypeMapper.TryGetMapping(baseType, out var mapping)) + continue; + + var row = table.Rows.Add(); + + PopulateDefaultDataTypeInfo(row, baseType); + PopulateHardcodedDataTypeInfo(row, baseType); + + if (mapping.ClrTypes.Length > 0) + row["DataType"] = mapping.ClrTypes[0].FullName; + if (mapping.NpgsqlDbType.HasValue) + row["ProviderDbType"] = (int)mapping.NpgsqlDbType.Value; + } + + foreach (var arrayType in connector.DatabaseInfo.ArrayTypes) + { + if (!connector.TypeMapper.TryGetMapping(arrayType.Element, out var elementMapping)) + continue; + + var row = table.Rows.Add(); + + PopulateDefaultDataTypeInfo(row, arrayType.Element); + // Populate hardcoded values based on the element type (e.g. citext[] is case-insensitive). + PopulateHardcodedDataTypeInfo(row, arrayType.Element); + + row["TypeName"] = arrayType.DisplayName; + row["OID"] = arrayType.OID; + row["CreateFormat"] += "[]"; + if (elementMapping.ClrTypes.Length > 0) + row["DataType"] = elementMapping.ClrTypes[0].MakeArrayType().FullName; + if (elementMapping.NpgsqlDbType.HasValue) + row["ProviderDbType"] = (int)(elementMapping.NpgsqlDbType.Value | NpgsqlDbType.Array); + } + + foreach (var rangeType in connector.DatabaseInfo.RangeTypes) + { + if (!connector.TypeMapper.TryGetMapping(rangeType.Subtype, out var subtypeMapping)) + continue; + + var row = table.Rows.Add(); + + PopulateDefaultDataTypeInfo(row, rangeType.Subtype); + // Populate hardcoded values based on the subtype type (e.g. citext[] is case-insensitive). + PopulateHardcodedDataTypeInfo(row, rangeType.Subtype); + + row["TypeName"] = rangeType.DisplayName; + row["OID"] = rangeType.OID; + row["CreateFormat"] = rangeType.DisplayName.ToUpperInvariant(); + if (subtypeMapping.ClrTypes.Length > 0) + row["DataType"] = typeof(NpgsqlRange<>).MakeGenericType(subtypeMapping.ClrTypes[0]).FullName; + if (subtypeMapping.NpgsqlDbType.HasValue) + row["ProviderDbType"] = (int)(subtypeMapping.NpgsqlDbType.Value | NpgsqlDbType.Range); + } + + foreach (var multirangeType in connector.DatabaseInfo.MultirangeTypes) + { + var subtypeType = multirangeType.Subrange.Subtype; + if (!connector.TypeMapper.TryGetMapping(subtypeType, out var subtypeMapping)) + continue; + + var row = table.Rows.Add(); + + PopulateDefaultDataTypeInfo(row, subtypeType); + // Populate hardcoded values based on the subtype type (e.g. citext[] is case-insensitive). + PopulateHardcodedDataTypeInfo(row, subtypeType); + + row["TypeName"] = multirangeType.DisplayName; + row["OID"] = multirangeType.OID; + row["CreateFormat"] = multirangeType.DisplayName.ToUpperInvariant(); + if (subtypeMapping.ClrTypes.Length > 0) + row["DataType"] = typeof(NpgsqlRange<>).MakeGenericType(subtypeMapping.ClrTypes[0]).FullName; + if (subtypeMapping.NpgsqlDbType.HasValue) + row["ProviderDbType"] = (int)(subtypeMapping.NpgsqlDbType.Value | NpgsqlDbType.Range); + } + + foreach (var domainType in connector.DatabaseInfo.DomainTypes) + { + if (!connector.TypeMapper.TryGetMapping(domainType, out var baseMapping)) + continue; + + var row = table.Rows.Add(); + + PopulateDefaultDataTypeInfo(row, domainType.BaseType); + // Populate hardcoded values based on the element type (e.g. citext[] is case-insensitive). + PopulateHardcodedDataTypeInfo(row, domainType.BaseType); + row["TypeName"] = domainType.DisplayName; + row["OID"] = domainType.OID; + // A domain is never the best match, since its underlying base type is + row["IsBestMatch"] = false; + + if (baseMapping.ClrTypes.Length > 0) + row["DataType"] = baseMapping.ClrTypes[0].FullName; + if (baseMapping.NpgsqlDbType.HasValue) + row["ProviderDbType"] = (int)baseMapping.NpgsqlDbType.Value; + } + + return table; + } + + /// + /// Populates some generic type information that is common for base types, arrays, enums, etc. Some will + /// be overridden later. + /// + static void PopulateDefaultDataTypeInfo(DataRow row, PostgresType type) + { + row["TypeName"] = type.DisplayName; + // Skipping ColumnSize at least for now, not very meaningful + row["CreateFormat"] = type.DisplayName.ToUpperInvariant(); + row["CreateParameters"] = ""; + row["IsAutoIncrementable"] = false; + // We populate the DataType above from mapping.ClrTypes, which means we take the .NET type from + // which we *infer* the PostgreSQL type. Since only a single PostgreSQL type gets inferred from a given + // .NET type, we never have the same DataType in more than one row - so the mapping is always the + // best match. See the hardcoding override below for some exceptions. + row["IsBestMatch"] = true; + row["IsCaseSensitive"] = true; + row["IsConcurrencyType"] = false; + row["IsFixedLength"] = false; + row["IsFixedPrecisionAndScale"] = false; + row["IsLiteralSupported"] = false; // See hardcoding override below + row["IsLong"] = false; + row["IsNullable"] = true; + row["IsSearchable"] = true; + row["IsSearchableWithLike"] = false; + row["IsUnsigned"] = DBNull.Value; // See hardcoding override below + // LiteralPrefix/Suffix: no literal for now except for strings, see hardcoding override below + row["MaximumScale"] = DBNull.Value; + row["MinimumScale"] = DBNull.Value; + // NativeDataType is unset + row["OID"] = type.OID; + } + + /// + /// Sets some custom, hardcoded info on a DataType row that cannot be loaded/inferred from PostgreSQL + /// + static void PopulateHardcodedDataTypeInfo(DataRow row, PostgresType type) + { + switch (type.Name) + { + case "varchar": + case "char": + row["DataType"] = "String"; + row["IsBestMatch"] = false; + goto case "text"; + case "text": + row["CreateFormat"] += "({0})"; + row["CreateParameters"] = "size"; + row["IsSearchableWithLike"] = true; + row["IsLiteralSupported"] = true; + row["LiteralPrefix"] = "'"; + row["LiteralSuffix"] = "'"; + return; + case "numeric": + row["CreateFormat"] += "({0},{1})"; + row["CreateParameters"] = "precision, scale"; + row["MaximumScale"] = 16383; + row["MinimumScale"] = 16383; + row["IsUnsigned"] = false; + return; + case "bytea": + row["IsLong"] = true; + return; + case "citext": + row["IsCaseSensitive"] = false; + return; + case "integer": + case "smallint": + case "bigint": + case "double precision": + case "real": + case "money": + row["IsUnsigned"] = false; + return; + case "oid": + case "cid": + case "regtype": + case "regconfig": + row["IsUnsigned"] = true; + return; + case "xid": + row["IsUnsigned"] = true; + row["IsConcurrencyType"] = true; + return; + } + } + + #endregion DataTypes + + #region Reserved Keywords + + static DataTable GetReservedWords() + { + var table = new DataTable("ReservedWords") { Locale = CultureInfo.InvariantCulture }; + table.Columns.Add("ReservedWord", typeof(string)); + foreach (var keyword in ReservedKeywords) + table.Rows.Add(keyword); + return table; + } + + /// + /// List of keywords taken from PostgreSQL 9.0 reserved words documentation. + /// + static readonly string[] ReservedKeywords = + { + "ALL", + "ANALYSE", + "ANALYZE", + "AND", + "ANY", + "ARRAY", + "AS", + "ASC", + "ASYMMETRIC", + "AUTHORIZATION", + "BINARY", + "BOTH", + "CASE", + "CAST", + "CHECK", + "COLLATE", + "COLUMN", + "CONCURRENTLY", + "CONSTRAINT", + "CREATE", + "CROSS", + "CURRENT_CATALOG", + "CURRENT_DATE", + "CURRENT_ROLE", + "CURRENT_SCHEMA", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "CURRENT_USER", + "DEFAULT", + "DEFERRABLE", + "DESC", + "DISTINCT", + "DO", + "ELSE", + "END", + "EXCEPT", + "FALSE", + "FETCH", + "FOR", + "FOREIGN", + "FREEZE", + "FROM", + "FULL", + "GRANT", + "GROUP", + "HAVING", + "ILIKE", + "IN", + "INITIALLY", + "INNER", + "INTERSECT", + "INTO", + "IS", + "ISNULL", + "JOIN", + "LATERAL", + "LEADING", + "LEFT", + "LIKE", + "LIMIT", + "LOCALTIME", + "LOCALTIMESTAMP", + "NATURAL", + "NOT", + "NOTNULL", + "NULL", + "OFFSET", + "ON", + "ONLY", + "OR", + "ORDER", + "OUTER", + "OVER", + "OVERLAPS", + "PLACING", + "PRIMARY", + "REFERENCES", + "RETURNING", + "RIGHT", + "SELECT", + "SESSION_USER", + "SIMILAR", + "SOME", + "SYMMETRIC", + "TABLE", + "THEN", + "TO", + "TRAILING", + "TRUE", + "UNION", + "UNIQUE", + "USER", + "USING", + "VARIADIC", + "VERBOSE", + "WHEN", + "WHERE", + "WINDOW", + "WITH" + }; + + #endregion Reserved Keywords +} diff --git a/LibExternal/Npgsql/NpgsqlSqlEventSource.cs b/LibExternal/Npgsql/NpgsqlSqlEventSource.cs new file mode 100644 index 0000000..d8dc66d --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlSqlEventSource.cs @@ -0,0 +1,30 @@ +using System.Diagnostics.Tracing; +using System.Runtime.CompilerServices; + +namespace Npgsql; + +sealed class NpgsqlSqlEventSource : EventSource +{ + public static readonly NpgsqlSqlEventSource Log = new(); + + const string EventSourceName = "Npgsql.Sql"; + + const int CommandStartId = 3; + const int CommandStopId = 4; + + internal NpgsqlSqlEventSource() : base(EventSourceName) {} + + // NOTE + // - The 'Start' and 'Stop' suffixes on the following event names have special meaning in EventSource. They + // enable creating 'activities'. + // For more information, take a look at the following blog post: + // https://blogs.msdn.microsoft.com/vancem/2015/09/14/exploring-eventsource-activity-correlation-and-causation-features/ + // - A stop event's event id must be next one after its start event. + + [Event(CommandStartId, Level = EventLevel.Informational)] + public void CommandStart(string sql) => Log.WriteEvent(CommandStartId, sql); + + [MethodImpl(MethodImplOptions.NoInlining)] + [Event(CommandStopId, Level = EventLevel.Informational)] + public void CommandStop() => Log.WriteEvent(CommandStopId); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlStrings.cs b/LibExternal/Npgsql/NpgsqlStrings.cs new file mode 100644 index 0000000..75e004d --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlStrings.cs @@ -0,0 +1,10 @@ +namespace Npgsql; + +internal static class NpgsqlStrings +{ + public const string CannotReadInfinityValue = "Cannot read infinity value since Npgsql.DisableDateTimeInfinityConversions is enabled."; + public const string CannotUseSslModeRequireWithoutTrustServerCertificate = "To validate server certificates, please use VerifyFull or VerifyCA instead of Require. To disable validation, explicitly set 'Trust Server Certificate' to true. See https://www.npgsql.org/doc/release-notes/6.0.html for more details."; + public const string CannotUseSslRootCertificateWithUserCallback = "RootCertificate cannot be used in conjunction with UserCertificateValidationCallback; when registering a validation callback, perform whatever validation you require in that callback."; + public const string CannotUseSslVerifyWithUserCallback = "SslMode.{0} cannot be used in conjunction with UserCertificateValidationCallback; when registering a validation callback, perform whatever validation you require in that callback."; + public const string CannotUseTrustServerCertificate = "TrustServerCertificate=true is not supported with SslMode={0}"; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlTransaction.cs b/LibExternal/Npgsql/NpgsqlTransaction.cs new file mode 100644 index 0000000..d3d5a1d --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlTransaction.cs @@ -0,0 +1,515 @@ +using System; +using System.Data; +using System.Data.Common; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.Internal; +using Npgsql.Logging; + +namespace Npgsql; + +/// +/// Represents a transaction to be made in a PostgreSQL database. This class cannot be inherited. +/// +public sealed class NpgsqlTransaction : DbTransaction +{ + #region Fields and Properties + + /// + /// Specifies the object associated with the transaction. + /// + /// The object associated with the transaction. + public new NpgsqlConnection? Connection + { + get + { + CheckDisposed(); + return _connector?.Connection; + } + } + + // Note that with ambient transactions, it's possible for a transaction to be pending after its connection + // is already closed. So we capture the connector and perform everything directly on it. + NpgsqlConnector _connector; + + /// + /// Specifies the object associated with the transaction. + /// + /// The object associated with the transaction. + protected override DbConnection? DbConnection => Connection; + + /// + /// If true, the transaction has been committed/rolled back, but not disposed. + /// + internal bool IsCompleted => _connector is null || _connector.TransactionStatus == TransactionStatus.Idle; + + internal bool IsDisposed; + + Exception? _disposeReason; + + /// + /// Specifies the isolation level for this transaction. + /// + /// The isolation level for this transaction. The default is . + public override IsolationLevel IsolationLevel + { + get + { + CheckReady(); + return _isolationLevel; + } + } + IsolationLevel _isolationLevel; + + static readonly NpgsqlLogger Log = NpgsqlLogManager.CreateLogger(nameof(NpgsqlTransaction)); + + const IsolationLevel DefaultIsolationLevel = IsolationLevel.ReadCommitted; + + #endregion + + #region Initialization + + internal NpgsqlTransaction(NpgsqlConnector connector) + => _connector = connector; + + internal void Init(IsolationLevel isolationLevel = DefaultIsolationLevel) + { + Debug.Assert(isolationLevel != IsolationLevel.Chaos); + + if (!_connector.DatabaseInfo.SupportsTransactions) + return; + + Log.Debug($"Beginning transaction with isolation level {isolationLevel}", _connector.Id); + switch (isolationLevel) + { + case IsolationLevel.RepeatableRead: + case IsolationLevel.Snapshot: + _connector.PrependInternalMessage(PregeneratedMessages.BeginTransRepeatableRead, 2); + break; + case IsolationLevel.Serializable: + _connector.PrependInternalMessage(PregeneratedMessages.BeginTransSerializable, 2); + break; + case IsolationLevel.ReadUncommitted: + // PG doesn't really support ReadUncommitted, it's the same as ReadCommitted. But we still + // send as if. + _connector.PrependInternalMessage(PregeneratedMessages.BeginTransReadUncommitted, 2); + break; + case IsolationLevel.ReadCommitted: + _connector.PrependInternalMessage(PregeneratedMessages.BeginTransReadCommitted, 2); + break; + case IsolationLevel.Unspecified: + isolationLevel = DefaultIsolationLevel; + goto case DefaultIsolationLevel; + default: + throw new NotSupportedException("Isolation level not supported: " + isolationLevel); + } + + _connector.TransactionStatus = TransactionStatus.Pending; + _isolationLevel = isolationLevel; + IsDisposed = false; + } + + #endregion + + #region Commit + + /// + /// Commits the database transaction. + /// + public override void Commit() => Commit(false).GetAwaiter().GetResult(); + + async Task Commit(bool async, CancellationToken cancellationToken = default) + { + CheckReady(); + + if (!_connector.DatabaseInfo.SupportsTransactions) + return; + + using (_connector.StartUserAction(cancellationToken)) + { + Log.Debug("Committing transaction", _connector.Id); + await _connector.ExecuteInternalCommand(PregeneratedMessages.CommitTransaction, async, cancellationToken); + } + } + + /// + /// Commits the database transaction. + /// + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// +#if NETSTANDARD2_0 + public Task CommitAsync(CancellationToken cancellationToken = default) +#else + public override Task CommitAsync(CancellationToken cancellationToken = default) +#endif + { + using (NoSynchronizationContextScope.Enter()) + return Commit(true, cancellationToken); + } + + #endregion + + #region Rollback + + /// + /// Rolls back a transaction from a pending state. + /// + public override void Rollback() => Rollback(false).GetAwaiter().GetResult(); + + async Task Rollback(bool async, CancellationToken cancellationToken = default) + { + CheckReady(); + + if (!_connector.DatabaseInfo.SupportsTransactions) + return; + + using (_connector.StartUserAction(cancellationToken)) + await _connector.Rollback(async, cancellationToken); + } + + /// + /// Rolls back a transaction from a pending state. + /// + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// +#if NETSTANDARD2_0 + public Task RollbackAsync(CancellationToken cancellationToken = default) +#else + public override Task RollbackAsync(CancellationToken cancellationToken = default) +#endif + { + using (NoSynchronizationContextScope.Enter()) + return Rollback(true, cancellationToken); + } + + #endregion + + #region Savepoints + + /// + /// Creates a transaction save point. + /// + /// The name of the savepoint. + /// + /// This method does not cause a database roundtrip to be made. The savepoint creation statement will instead be sent along with + /// the next command. + /// +#if NET5_0_OR_GREATER + public override void Save(string name) +#else + public void Save(string name) +#endif + { + if (name == null) + throw new ArgumentNullException(nameof(name)); + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentException("name can't be empty", nameof(name)); + + CheckReady(); + if (!_connector.DatabaseInfo.SupportsTransactions) + return; + + // Note that creating a savepoint doesn't actually send anything to the backend (only prepends), so strictly speaking we don't + // have to start a user action. However, we do this for consistency as if we did (for the checks and exceptions) + using var _ = _connector.StartUserAction(); + + Log.Debug($"Creating savepoint {name}", _connector.Id); + + if (RequiresQuoting(name)) + name = $"\"{name.Replace("\"", "\"\"")}\""; + + // Note: savepoint names are PostgreSQL identifiers, and so limited by default to 63 characters. + // Since we are prepending, we assume below that the statement will always fit in the buffer. + _connector.WriteBuffer.WriteByte(FrontendMessageCode.Query); + _connector.WriteBuffer.WriteInt32( + sizeof(int) + // Message length (including self excluding code) + _connector.TextEncoding.GetByteCount("SAVEPOINT ") + + _connector.TextEncoding.GetByteCount(name) + + sizeof(byte)); // Null terminator + + _connector.WriteBuffer.WriteString("SAVEPOINT "); + _connector.WriteBuffer.WriteString(name); + _connector.WriteBuffer.WriteByte(0); + + _connector.PendingPrependedResponses += 2; + } + + /// + /// Creates a transaction save point. + /// + /// The name of the savepoint. + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// + /// This method does not cause a database roundtrip to be made, and will therefore always complete synchronously. + /// The savepoint creation statement will instead be sent along with the next command. + /// +#if NET5_0_OR_GREATER + public override Task SaveAsync(string name, CancellationToken cancellationToken = default) +#else + public Task SaveAsync(string name, CancellationToken cancellationToken = default) +#endif + { + Save(name); + return Task.CompletedTask; + } + + async Task Rollback(string name, bool async, CancellationToken cancellationToken = default) + { + if (name == null) + throw new ArgumentNullException(nameof(name)); + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentException("name can't be empty", nameof(name)); + + CheckReady(); + if (!_connector.DatabaseInfo.SupportsTransactions) + return; + using (_connector.StartUserAction(cancellationToken)) + { + Log.Debug($"Rolling back savepoint {name}", _connector.Id); + + if (RequiresQuoting(name)) + name = $"\"{name.Replace("\"", "\"\"")}\""; + + await _connector.ExecuteInternalCommand($"ROLLBACK TO SAVEPOINT {name}", async, cancellationToken); + } + } + + /// + /// Rolls back a transaction from a pending savepoint state. + /// + /// The name of the savepoint. +#if NET5_0_OR_GREATER + public override void Rollback(string name) +#else + public void Rollback(string name) +#endif + => Rollback(name, false).GetAwaiter().GetResult(); + + /// + /// Rolls back a transaction from a pending savepoint state. + /// + /// The name of the savepoint. + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// +#if NET5_0_OR_GREATER + public override Task RollbackAsync(string name, CancellationToken cancellationToken = default) +#else + public Task RollbackAsync(string name, CancellationToken cancellationToken = default) +#endif + { + using (NoSynchronizationContextScope.Enter()) + return Rollback(name, true, cancellationToken); + } + + async Task Release(string name, bool async, CancellationToken cancellationToken = default) + { + if (name == null) + throw new ArgumentNullException(nameof(name)); + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentException("name can't be empty", nameof(name)); + + CheckReady(); + if (!_connector.DatabaseInfo.SupportsTransactions) + return; + using (_connector.StartUserAction(cancellationToken)) + { + Log.Debug($"Releasing savepoint {name}", _connector.Id); + + if (RequiresQuoting(name)) + name = $"\"{name.Replace("\"", "\"\"")}\""; + + await _connector.ExecuteInternalCommand($"RELEASE SAVEPOINT {name}", async, cancellationToken); + } + } + + /// + /// Releases a transaction from a pending savepoint state. + /// + /// The name of the savepoint. +#if NET5_0_OR_GREATER + public override void Release(string name) => Release(name, false).GetAwaiter().GetResult(); +#else + public void Release(string name) => Release(name, false).GetAwaiter().GetResult(); +#endif + + /// + /// Releases a transaction from a pending savepoint state. + /// + /// The name of the savepoint. + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// +#if NET5_0_OR_GREATER + public override Task ReleaseAsync(string name, CancellationToken cancellationToken = default) +#else + public Task ReleaseAsync(string name, CancellationToken cancellationToken = default) +#endif + { + using (NoSynchronizationContextScope.Enter()) + return Release(name, true, cancellationToken); + } + + /// + /// Indicates whether this transaction supports database savepoints. + /// +#if NET5_0_OR_GREATER + public override bool SupportsSavepoints +#else + public bool SupportsSavepoints +#endif + { + get => _connector.DatabaseInfo.SupportsTransactions; + } + + #endregion + + #region Dispose + + /// + /// Disposes the transaction, rolling it back if it is still pending. + /// + protected override void Dispose(bool disposing) + { + if (IsDisposed) + return; + + if (disposing) + { + if (!IsCompleted) + { + try + { + _connector.CloseOngoingOperations(async: false).GetAwaiter().GetResult(); + Rollback(); + } + catch (Exception ex) + { + Debug.Assert(_connector.IsBroken); + Log.Error("Exception while disposing a transaction", ex, _connector.Id); + } + } + + IsDisposed = true; + _connector?.Connection?.EndBindingScope(ConnectorBindingScope.Transaction); + } + } + + /// + /// Disposes the transaction, rolling it back if it is still pending. + /// +#if NETSTANDARD2_0 + public ValueTask DisposeAsync() +#else + public override ValueTask DisposeAsync() +#endif + { + if (!IsDisposed) + { + if (!IsCompleted) + { + using (NoSynchronizationContextScope.Enter()) + return DisposeAsyncInternal(); + } + + IsDisposed = true; + _connector?.Connection?.EndBindingScope(ConnectorBindingScope.Transaction); + } + return default; + + async ValueTask DisposeAsyncInternal() + { + // We're disposing, so no cancellation token + try + { + await _connector.CloseOngoingOperations(async: true); + await Rollback(async: true); + } + catch (Exception ex) + { + Debug.Assert(_connector.IsBroken); + Log.Error("Exception while disposing a transaction", ex, _connector.Id); + } + + IsDisposed = true; + _connector?.Connection?.EndBindingScope(ConnectorBindingScope.Transaction); + } + } + + /// + /// Disposes the transaction, without rolling back. Used only in special circumstances, e.g. when + /// the connection is broken. + /// + internal void DisposeImmediately(Exception? disposeReason) + { + IsDisposed = true; + _disposeReason = disposeReason; + } + + #endregion + + #region Checks + + void CheckReady() + { + CheckDisposed(); + if (IsCompleted) + throw new InvalidOperationException("This NpgsqlTransaction has completed; it is no longer usable."); + } + + void CheckDisposed() + { + if (IsDisposed) + throw new ObjectDisposedException(typeof(NpgsqlTransaction).Name, _disposeReason); + } + + static bool RequiresQuoting(string identifier) + { + Debug.Assert(identifier.Length > 0); + + var first = identifier[0]; + if (first != '_' && !char.IsLower(first)) + return true; + + foreach (var c in identifier.AsSpan(1)) + if (c != '_' && c != '$' && !char.IsLower(c) && !char.IsDigit(c)) + return true; + + return false; + } + + #endregion + + #region Misc + + /// + /// Unbinds transaction from the connector. + /// Should be called before the connector is returned to the pool. + /// + internal void UnbindIfNecessary() + { + // We're closing the connection, but transaction is not yet disposed + // We have to unbind the transaction from the connector, otherwise there could be a concurrency issues + // See #3306 + if (!IsDisposed) + { + if (_connector.UnboundTransaction is { IsDisposed: true } previousTransaction) + { + previousTransaction._connector = _connector; + _connector.Transaction = previousTransaction; + } + else + _connector.Transaction = null; + + _connector.UnboundTransaction = this; + _connector = null!; + } + } + + #endregion +} diff --git a/LibExternal/Npgsql/NpgsqlTypes/NpgsqlDate.cs b/LibExternal/Npgsql/NpgsqlTypes/NpgsqlDate.cs new file mode 100644 index 0000000..bd0442a --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlTypes/NpgsqlDate.cs @@ -0,0 +1,480 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +#pragma warning disable 1591 + +// ReSharper disable once CheckNamespace +namespace NpgsqlTypes; + +[Obsolete( + "For values outside the range of DateTime/DateOnly, consider using NodaTime (range -9998 to 9999), or read the value as an 'int'. " + + "See https://www.npgsql.org/doc/types/datetime.html for more information.")] +[Serializable] +public readonly struct NpgsqlDate : IEquatable, IComparable, IComparable, + IComparer, IComparer +{ + //Number of days since January 1st CE (January 1st EV). 1 Jan 1 CE = 0, 2 Jan 1 CE = 1, 31 Dec 1 BCE = -1, etc. + readonly int _daysSinceEra; + readonly InternalType _type; + + #region Constants + + static readonly int[] CommonYearDays = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }; + static readonly int[] LeapYearDays = { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }; + static readonly int[] CommonYearMaxes = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + static readonly int[] LeapYearMaxes = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + + /// + /// Represents the date 1970-01-01 + /// + public static readonly NpgsqlDate Epoch = new(1970, 1, 1); + + /// + /// Represents the date 0001-01-01 + /// + public static readonly NpgsqlDate Era = new(0); + + public const int MaxYear = 5874897; + public const int MinYear = -4714; + public static readonly NpgsqlDate MaxCalculableValue = new(MaxYear, 12, 31); + public static readonly NpgsqlDate MinCalculableValue = new(MinYear, 11, 24); + + public static readonly NpgsqlDate Infinity = new(InternalType.Infinity); + public static readonly NpgsqlDate NegativeInfinity = new(InternalType.NegativeInfinity); + + const int DaysInYear = 365; //Common years + const int DaysIn4Years = 4 * DaysInYear + 1; //Leap year every 4 years. + const int DaysInCentury = 25 * DaysIn4Years - 1; //Except no leap year every 100. + const int DaysIn4Centuries = 4 * DaysInCentury + 1; //Except leap year every 400. + + #endregion + + #region Constructors + + NpgsqlDate(InternalType type) + { + _type = type; + _daysSinceEra = 0; + } + + internal NpgsqlDate(int days) + { + _type = InternalType.Finite; + _daysSinceEra = days; + } + + public NpgsqlDate(DateTime dateTime) : this((int)(dateTime.Ticks / TimeSpan.TicksPerDay)) {} + + public NpgsqlDate(NpgsqlDate copyFrom) : this(copyFrom._daysSinceEra) {} + + public NpgsqlDate(int year, int month, int day) + { + _type = InternalType.Finite; + if (year == 0 || year < MinYear || year > MaxYear || month < 1 || month > 12 || day < 1 || + (day > (IsLeap(year) ? 366 : 365))) + { + throw new ArgumentOutOfRangeException(); + } + + _daysSinceEra = DaysForYears(year) + (IsLeap(year) ? LeapYearDays : CommonYearDays)[month - 1] + day - 1; + } + + #endregion + + #region String Conversions + + public override string ToString() + => _type switch + { + InternalType.Infinity => "infinity", + InternalType.NegativeInfinity => "-infinity", + //Format of yyyy-MM-dd with " BC" for BCE and optional " AD" for CE which we omit here. + _ => new StringBuilder(Math.Abs(Year).ToString("D4")) + .Append('-').Append(Month.ToString("D2")) + .Append('-').Append(Day.ToString("D2")) + .Append(_daysSinceEra < 0 ? " BC" : "").ToString() + }; + + public static NpgsqlDate Parse(string str) + { + + if (str == null) { + throw new ArgumentNullException(nameof(str)); + } + + if (str == "infinity") + return Infinity; + + if (str == "-infinity") + return NegativeInfinity; + + str = str.Trim(); + try { + var idx = str.IndexOf('-'); + if (idx == -1) { + throw new FormatException(); + } + var year = int.Parse(str.Substring(0, idx)); + var idxLast = idx + 1; + if ((idx = str.IndexOf('-', idxLast)) == -1) { + throw new FormatException(); + } + var month = int.Parse(str.Substring(idxLast, idx - idxLast)); + idxLast = idx + 1; + if ((idx = str.IndexOf(' ', idxLast)) == -1) { + idx = str.Length; + } + var day = int.Parse(str.Substring(idxLast, idx - idxLast)); + if (str.Contains("BC")) { + year = -year; + } + return new NpgsqlDate(year, month, day); + } catch (OverflowException) { + throw; + } catch (Exception) { + throw new FormatException(); + } + } + + public static bool TryParse(string str, out NpgsqlDate date) + { + try { + date = Parse(str); + return true; + } catch { + date = Era; + return false; + } + } + + #endregion + + #region Public Properties + + public static NpgsqlDate Now => new(DateTime.Now); + public static NpgsqlDate Today => Now; + public static NpgsqlDate Yesterday => Now.AddDays(-1); + public static NpgsqlDate Tomorrow => Now.AddDays(1); + + public int DayOfYear => _daysSinceEra - DaysForYears(Year) + 1; + + public int Year + { + get + { + var guess = (int)Math.Round(_daysSinceEra/365.2425); + var test = guess - 1; + while (DaysForYears(++test) <= _daysSinceEra) {} + return test - 1; + } + } + + public int Month + { + get + { + var i = 1; + var target = DayOfYear; + var array = IsLeapYear ? LeapYearDays : CommonYearDays; + while (target > array[i]) + { + ++i; + } + return i; + } + } + + public int Day => DayOfYear - (IsLeapYear ? LeapYearDays : CommonYearDays)[Month - 1]; + + public DayOfWeek DayOfWeek => (DayOfWeek) ((_daysSinceEra + 1)%7); + + internal int DaysSinceEra => _daysSinceEra; + + public bool IsLeapYear => IsLeap(Year); + + public bool IsInfinity => _type == InternalType.Infinity; + public bool IsNegativeInfinity => _type == InternalType.NegativeInfinity; + + public bool IsFinite + => _type switch { + InternalType.Finite => true, + InternalType.Infinity => false, + InternalType.NegativeInfinity => false, + _ => throw new InvalidOperationException($"Internal Npgsql bug: unexpected value {_type} of enum {nameof(NpgsqlDate)}.{nameof(InternalType)}. Please file a bug.") + }; + + #endregion + + #region Internals + + static int DaysForYears(int years) + { + //Number of years after 1CE (0 for 1CE, -1 for 1BCE, 1 for 2CE). + var calcYear = years < 1 ? years : years - 1; + + return calcYear / 400 * DaysIn4Centuries //Blocks of 400 years with their leap and common years + + calcYear % 400 / 100 * DaysInCentury //Remaining blocks of 100 years with their leap and common years + + calcYear % 100 / 4 * DaysIn4Years //Remaining blocks of 4 years with their leap and common years + + calcYear % 4 * DaysInYear //Remaining years, all common + + (calcYear < 0 ? -1 : 0); //And 1BCE is leap. + } + + static bool IsLeap(int year) + { + //Every 4 years is a leap year + //Except every 100 years isn't a leap year. + //Except every 400 years is. + if (year < 1) + { + year = year + 1; + } + return (year%4 == 0) && ((year%100 != 0) || (year%400 == 0)); + } + + #endregion + + #region Arithmetic + + public NpgsqlDate AddDays(int days) + => _type switch + { + InternalType.Infinity => Infinity, + InternalType.NegativeInfinity => NegativeInfinity, + InternalType.Finite => new NpgsqlDate(_daysSinceEra + days), + _ => throw new InvalidOperationException($"Internal Npgsql bug: unexpected value {_type} of enum {nameof(NpgsqlDate)}.{nameof(InternalType)}. Please file a bug.") + }; + + public NpgsqlDate AddYears(int years) + { + switch (_type) { + case InternalType.Infinity: + return Infinity; + case InternalType.NegativeInfinity: + return NegativeInfinity; + case InternalType.Finite: + break; + default: + throw new InvalidOperationException($"Internal Npgsql bug: unexpected value {_type} of enum {nameof(NpgsqlDate)}.{nameof(InternalType)}. Please file a bug."); + } + + var newYear = Year + years; + if (newYear >= 0 && _daysSinceEra < 0) //cross 1CE/1BCE divide going up + { + ++newYear; + } + else if (newYear <= 0 && _daysSinceEra >= 0) //cross 1CE/1BCE divide going down + { + --newYear; + } + return new NpgsqlDate(newYear, Month, Day); + } + + public NpgsqlDate AddMonths(int months) + { + switch (_type) { + case InternalType.Infinity: + return Infinity; + case InternalType.NegativeInfinity: + return NegativeInfinity; + case InternalType.Finite: + break; + default: + throw new InvalidOperationException($"Internal Npgsql bug: unexpected value {_type} of enum {nameof(NpgsqlDate)}.{nameof(InternalType)}. Please file a bug."); + } + + var newYear = Year; + var newMonth = Month + months; + + while (newMonth > 12) + { + newMonth -= 12; + newYear += 1; + } + while (newMonth < 1) + { + newMonth += 12; + newYear -= 1; + } + var maxDay = (IsLeap(newYear) ? LeapYearMaxes : CommonYearMaxes)[newMonth - 1]; + var newDay = Day > maxDay ? maxDay : Day; + return new NpgsqlDate(newYear, newMonth, newDay); + + } + + public NpgsqlDate Add(in NpgsqlTimeSpan interval) + { + switch (_type) { + case InternalType.Infinity: + return Infinity; + case InternalType.NegativeInfinity: + return NegativeInfinity; + case InternalType.Finite: + break; + default: + throw new InvalidOperationException($"Internal Npgsql bug: unexpected value {_type} of enum {nameof(NpgsqlDate)}.{nameof(InternalType)}. Please file a bug."); + } + + return AddMonths(interval.Months).AddDays(interval.Days); + } + + internal NpgsqlDate Add(in NpgsqlTimeSpan interval, int carriedOverflow) + { + switch (_type) { + case InternalType.Infinity: + return Infinity; + case InternalType.NegativeInfinity: + return NegativeInfinity; + case InternalType.Finite: + break; + default: + throw new InvalidOperationException($"Internal Npgsql bug: unexpected value {_type} of enum {nameof(NpgsqlDate)}.{nameof(InternalType)}. Please file a bug."); + } + + return AddMonths(interval.Months).AddDays(interval.Days + carriedOverflow); + } + + #endregion + + #region Comparison + + public int Compare(NpgsqlDate x, NpgsqlDate y) => x.CompareTo(y); + + public int Compare(object? x, object? y) + { + if (x == null) + { + return y == null ? 0 : -1; + } + if (y == null) + { + return 1; + } + if (!(x is IComparable) || !(y is IComparable)) + { + throw new ArgumentException(); + } + return ((IComparable) x).CompareTo(y); + } + + public bool Equals(NpgsqlDate other) + => _type switch + { + InternalType.Infinity => other._type == InternalType.Infinity, + InternalType.NegativeInfinity => other._type == InternalType.NegativeInfinity, + InternalType.Finite => other._type == InternalType.Finite && _daysSinceEra == other._daysSinceEra, + _ => false + }; + + public override bool Equals(object? obj) => obj is NpgsqlDate date && Equals(date); + + public int CompareTo(NpgsqlDate other) + => _type switch + { + InternalType.Infinity => other._type == InternalType.Infinity ? 0 : 1, + InternalType.NegativeInfinity => other._type == InternalType.NegativeInfinity ? 0 : -1, + _ => other._type switch + { + InternalType.Infinity => -1, + InternalType.NegativeInfinity => 1, + _ => _daysSinceEra.CompareTo(other._daysSinceEra) + } + }; + + public int CompareTo(object? o) + => o == null + ? 1 + : o is NpgsqlDate npgsqlDate + ? CompareTo(npgsqlDate) + : throw new ArgumentException(); + + public override int GetHashCode() => _daysSinceEra; + + #endregion + + #region Operators + + public static bool operator ==(NpgsqlDate x, NpgsqlDate y) => x.Equals(y); + public static bool operator !=(NpgsqlDate x, NpgsqlDate y) => !(x == y); + public static bool operator <(NpgsqlDate x, NpgsqlDate y) => x.CompareTo(y) < 0; + public static bool operator >(NpgsqlDate x, NpgsqlDate y) => x.CompareTo(y) > 0; + public static bool operator <=(NpgsqlDate x, NpgsqlDate y) => x.CompareTo(y) <= 0; + public static bool operator >=(NpgsqlDate x, NpgsqlDate y) => x.CompareTo(y) >= 0; + + public static DateTime ToDateTime(NpgsqlDate date) + { + switch (date._type) + { + case InternalType.Infinity: + case InternalType.NegativeInfinity: + throw new InvalidCastException("Infinity values can't be cast to DateTime"); + case InternalType.Finite: + try { return new DateTime(date._daysSinceEra * NpgsqlTimeSpan.TicksPerDay); } + catch { throw new InvalidCastException(); } + default: + throw new InvalidOperationException($"Internal Npgsql bug: unexpected value {date._type} of enum {nameof(NpgsqlDate)}.{nameof(InternalType)}. Please file a bug."); + } + } + + public static explicit operator DateTime(NpgsqlDate date) => ToDateTime(date); + + public static NpgsqlDate ToNpgsqlDate(DateTime date) + => new((int)(date.Ticks / NpgsqlTimeSpan.TicksPerDay)); + + public static explicit operator NpgsqlDate(DateTime date) => ToNpgsqlDate(date); + + public static NpgsqlDate operator +(NpgsqlDate date, NpgsqlTimeSpan interval) + => date.Add(interval); + + public static NpgsqlDate operator +(NpgsqlTimeSpan interval, NpgsqlDate date) + => date.Add(interval); + + public static NpgsqlDate operator -(NpgsqlDate date, NpgsqlTimeSpan interval) + => date.Subtract(interval); + + public NpgsqlDate Subtract(in NpgsqlTimeSpan interval) => Add(-interval); + + public static NpgsqlTimeSpan operator -(NpgsqlDate dateX, NpgsqlDate dateY) + { + if (dateX._type != InternalType.Finite || dateY._type != InternalType.Finite) + throw new ArgumentException("Can't subtract infinity date values"); + + return new NpgsqlTimeSpan(0, dateX._daysSinceEra - dateY._daysSinceEra, 0); + } + + #endregion + +#if NET6_0_OR_GREATER + public NpgsqlDate(DateOnly date) : this(date.Year, date.Month, date.Day) {} + + public static DateOnly ToDateOnly(NpgsqlDate date) + { + switch (date._type) + { + case InternalType.Infinity: + case InternalType.NegativeInfinity: + throw new InvalidCastException("Infinity values can't be cast to DateTime"); + case InternalType.Finite: + try { return new DateOnly(date.Year, date.Month, date.Day); } + catch { throw new InvalidCastException(); } + default: + throw new InvalidOperationException($"Internal Npgsql bug: unexpected value {date._type} of enum {nameof(NpgsqlDate)}.{nameof(InternalType)}. Please file a bug."); + } + } + + public static explicit operator DateOnly(NpgsqlDate date) => ToDateOnly(date); + + public static NpgsqlDate ToNpgsqlDate(DateOnly date) + => new(date.Year, date.Month, date.Day); + + public static explicit operator NpgsqlDate(DateOnly date) => ToNpgsqlDate(date); +#endif + + enum InternalType + { + Finite, + Infinity, + NegativeInfinity + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlTypes/NpgsqlDateTime.cs b/LibExternal/Npgsql/NpgsqlTypes/NpgsqlDateTime.cs new file mode 100644 index 0000000..927d210 --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlTypes/NpgsqlDateTime.cs @@ -0,0 +1,504 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Npgsql.Util; + +#pragma warning disable 1591 + +// ReSharper disable once CheckNamespace +namespace NpgsqlTypes; + +/// +/// A struct similar to .NET DateTime but capable of storing PostgreSQL's timestamp and timestamptz types. +/// DateTime is capable of storing values from year 1 to 9999 at 100-nanosecond precision, +/// while PostgreSQL's timestamps store values from 4713BC to 5874897AD with 1-microsecond precision. +/// +[Obsolete( + "For values outside the range of DateTime, consider using NodaTime (range -9998 to 9999), or read the value as a 'long'. " + + "See https://www.npgsql.org/doc/types/datetime.html for more information.")] +[Serializable] +public readonly struct NpgsqlDateTime : IEquatable, IComparable, IComparable, + IComparer, IComparer +{ + #region Fields + + readonly NpgsqlDate _date; + readonly TimeSpan _time; + readonly InternalType _type; + + #endregion + + #region Constants + + public static readonly NpgsqlDateTime Epoch = new(NpgsqlDate.Epoch); + public static readonly NpgsqlDateTime Era = new(NpgsqlDate.Era); + + public static readonly NpgsqlDateTime Infinity = + new(InternalType.Infinity, NpgsqlDate.Era, TimeSpan.Zero); + + public static readonly NpgsqlDateTime NegativeInfinity = + new(InternalType.NegativeInfinity, NpgsqlDate.Era, TimeSpan.Zero); + + // 9999-12-31 + const int MaxDateTimeDay = 3652058; + + #endregion + + #region Constructors + + NpgsqlDateTime(InternalType type, NpgsqlDate date, TimeSpan time) + { + if (!date.IsFinite && type != InternalType.Infinity && type != InternalType.NegativeInfinity) + throw new ArgumentException("Can't construct an NpgsqlDateTime with a non-finite date, use Infinity and NegativeInfinity instead", nameof(date)); + + _type = type; + _date = date; + _time = time; + } + + public NpgsqlDateTime(NpgsqlDate date, TimeSpan time, DateTimeKind kind = DateTimeKind.Unspecified) + : this(KindToInternalType(kind), date, time) {} + + public NpgsqlDateTime(NpgsqlDate date) + : this(date, TimeSpan.Zero) {} + + public NpgsqlDateTime(int year, int month, int day, int hours, int minutes, int seconds, DateTimeKind kind=DateTimeKind.Unspecified) + : this(new NpgsqlDate(year, month, day), new TimeSpan(0, hours, minutes, seconds), kind) {} + + public NpgsqlDateTime(int year, int month, int day, int hours, int minutes, int seconds, int milliseconds, DateTimeKind kind = DateTimeKind.Unspecified) + : this(new NpgsqlDate(year, month, day), new TimeSpan(0, hours, minutes, seconds, milliseconds), kind) { } + + public NpgsqlDateTime(DateTime dateTime) + : this(new NpgsqlDate(dateTime.Date), dateTime.TimeOfDay, dateTime.Kind) {} + + public NpgsqlDateTime(long ticks, DateTimeKind kind) + : this(new DateTime(ticks, kind)) { } + + public NpgsqlDateTime(long ticks) + : this(new DateTime(ticks, DateTimeKind.Unspecified)) { } + + #endregion + + #region Public Properties + + public NpgsqlDate Date => _date; + public TimeSpan Time => _time; + public int DayOfYear => _date.DayOfYear; + public int Year => _date.Year; + public int Month => _date.Month; + public int Day => _date.Day; + public DayOfWeek DayOfWeek => _date.DayOfWeek; + public bool IsLeapYear => _date.IsLeapYear; + + public long Ticks => _date.DaysSinceEra * NpgsqlTimeSpan.TicksPerDay + _time.Ticks; + public int Millisecond => _time.Milliseconds; + public int Second => _time.Seconds; + public int Minute => _time.Minutes; + public int Hour => _time.Hours; + public bool IsInfinity => _type == InternalType.Infinity; + public bool IsNegativeInfinity => _type == InternalType.NegativeInfinity; + + public bool IsFinite + => _type switch + { + InternalType.FiniteUnspecified => true, + InternalType.FiniteUtc => true, + InternalType.FiniteLocal => true, + InternalType.Infinity => false, + InternalType.NegativeInfinity => false, + _ => throw new InvalidOperationException($"Internal Npgsql bug: unexpected value {_type} of enum {nameof(NpgsqlDateTime)}.{nameof(InternalType)}. Please file a bug.") + }; + + public DateTimeKind Kind + => _type switch + { + InternalType.FiniteUtc => DateTimeKind.Utc, + InternalType.FiniteLocal => DateTimeKind.Local, + InternalType.FiniteUnspecified => DateTimeKind.Unspecified, + InternalType.Infinity => DateTimeKind.Unspecified, + InternalType.NegativeInfinity => DateTimeKind.Unspecified, + _ => throw new InvalidOperationException($"Internal Npgsql bug: unexpected value {_type} of enum {nameof(DateTimeKind)}. Please file a bug.") + }; + + /// + /// Cast of an to a . + /// + /// An equivalent . + public DateTime ToDateTime() + { + if (!IsFinite) + throw new InvalidCastException("Can't convert infinite timestamp values to DateTime"); + + if (_date.DaysSinceEra < 0 || _date.DaysSinceEra > MaxDateTimeDay) + throw new InvalidCastException("Out of the range of DateTime (year must be between 1 and 9999)"); + + return new DateTime(Ticks, Kind); + } + + /// + /// Converts the value of the current object to Coordinated Universal Time (UTC). + /// + /// + /// See the MSDN documentation for DateTime.ToUniversalTime(). + /// Note: this method only takes into account the time zone's base offset, and does + /// not respect daylight savings. See https://github.com/npgsql/npgsql/pull/684 for more + /// details. + /// + public NpgsqlDateTime ToUniversalTime() + { + switch (_type) + { + case InternalType.FiniteUnspecified: + // Treat as Local + case InternalType.FiniteLocal: + if (_date.DaysSinceEra >= 1 && _date.DaysSinceEra <= MaxDateTimeDay - 1) + { + // Day between 0001-01-02 and 9999-12-30, so we can use DateTime and it will always succeed + return new NpgsqlDateTime(Subtract(TimeZoneInfo.Local.GetUtcOffset(new DateTime(ToDateTime().Ticks, DateTimeKind.Local))).Ticks, DateTimeKind.Utc); + } + // Else there are no DST rules available in the system for outside the DateTime range, so just use the base offset + var timeTicks = _time.Ticks - TimeZoneInfo.Local.BaseUtcOffset.Ticks; + var date = _date; + if (timeTicks < 0) + { + timeTicks += NpgsqlTimeSpan.TicksPerDay; + date = date.AddDays(-1); + } + else if (timeTicks > NpgsqlTimeSpan.TicksPerDay) + { + timeTicks -= NpgsqlTimeSpan.TicksPerDay; + date = date.AddDays(1); + } + return new NpgsqlDateTime(date, TimeSpan.FromTicks(timeTicks), DateTimeKind.Utc); + case InternalType.FiniteUtc: + case InternalType.Infinity: + case InternalType.NegativeInfinity: + return this; + default: + throw new InvalidOperationException($"Internal Npgsql bug: unexpected value {_type} of enum {nameof(NpgsqlDateTime)}.{nameof(InternalType)}. Please file a bug."); + } + } + + /// + /// Converts the value of the current object to local time. + /// + /// + /// See the MSDN documentation for DateTime.ToLocalTime(). + /// Note: this method only takes into account the time zone's base offset, and does + /// not respect daylight savings. See https://github.com/npgsql/npgsql/pull/684 for more + /// details. + /// + public NpgsqlDateTime ToLocalTime() + { + switch (_type) { + case InternalType.FiniteUnspecified: + // Treat as UTC + case InternalType.FiniteUtc: + if (_date.DaysSinceEra >= 1 && _date.DaysSinceEra <= MaxDateTimeDay - 1) + { + // Day between 0001-01-02 and 9999-12-30, so we can use DateTime and it will always succeed + return new NpgsqlDateTime(TimeZoneInfo.ConvertTime(new DateTime(ToDateTime().Ticks, DateTimeKind.Utc), TimeZoneInfo.Local)); + } + // Else there are no DST rules available in the system for outside the DateTime range, so just use the base offset + var timeTicks = _time.Ticks + TimeZoneInfo.Local.BaseUtcOffset.Ticks; + var date = _date; + if (timeTicks < 0) + { + timeTicks += NpgsqlTimeSpan.TicksPerDay; + date = date.AddDays(-1); + } + else if (timeTicks > NpgsqlTimeSpan.TicksPerDay) + { + timeTicks -= NpgsqlTimeSpan.TicksPerDay; + date = date.AddDays(1); + } + return new NpgsqlDateTime(date, TimeSpan.FromTicks(timeTicks), DateTimeKind.Local); + case InternalType.FiniteLocal: + case InternalType.Infinity: + case InternalType.NegativeInfinity: + return this; + default: + throw new InvalidOperationException($"Internal Npgsql bug: unexpected value {_type} of enum {nameof(NpgsqlDateTime)}.{nameof(InternalType)}. Please file a bug."); + } + } + + public static NpgsqlDateTime Now => new(DateTime.Now); + + #endregion + + #region String Conversions + + public override string ToString() + => _type switch + { + InternalType.Infinity => "infinity", + InternalType.NegativeInfinity => "-infinity", + _ => $"{_date} {_time}" + }; + + public static NpgsqlDateTime Parse(string str) + { + if (str == null) { + throw new NullReferenceException(); + } + switch (str = str.Trim().ToLowerInvariant()) { + case "infinity": + return Infinity; + case "-infinity": + return NegativeInfinity; + default: + try { + var idxSpace = str.IndexOf(' '); + var datePart = str.Substring(0, idxSpace); + if (str.Contains("bc")) { + datePart += " BC"; + } + var idxSecond = str.IndexOf(' ', idxSpace + 1); + if (idxSecond == -1) { + idxSecond = str.Length; + } + var timePart = str.Substring(idxSpace + 1, idxSecond - idxSpace - 1); + return new NpgsqlDateTime(NpgsqlDate.Parse(datePart), TimeSpan.Parse(timePart)); + } catch (OverflowException) { + throw; + } catch { + throw new FormatException(); + } + } + } + + #endregion + + #region Comparisons + + public bool Equals(NpgsqlDateTime other) + => _type switch + { + InternalType.Infinity => other._type == InternalType.Infinity, + InternalType.NegativeInfinity => other._type == InternalType.NegativeInfinity, + _ => other._type == _type && _date.Equals(other._date) && _time.Equals(other._time) + }; + + public override bool Equals(object? obj) + => obj is NpgsqlDateTime time && Equals(time); + + public override int GetHashCode() + => _type switch + { + InternalType.Infinity => int.MaxValue, + InternalType.NegativeInfinity => int.MinValue, + _ => _date.GetHashCode() ^ PGUtil.RotateShift(_time.GetHashCode(), 16) + }; + + public int CompareTo(NpgsqlDateTime other) + { + switch (_type) { + case InternalType.Infinity: + return other._type == InternalType.Infinity ? 0 : 1; + case InternalType.NegativeInfinity: + return other._type == InternalType.NegativeInfinity ? 0 : -1; + default: + switch (other._type) { + case InternalType.Infinity: + return -1; + case InternalType.NegativeInfinity: + return 1; + default: + var cmp = _date.CompareTo(other._date); + return cmp == 0 ? _time.CompareTo(other._time) : cmp; + } + } + } + + public int CompareTo(object? o) + => o == null + ? 1 + : o is NpgsqlDateTime npgsqlDateTime + ? CompareTo(npgsqlDateTime) + : throw new ArgumentException(); + + public int Compare(NpgsqlDateTime x, NpgsqlDateTime y) => x.CompareTo(y); + + public int Compare(object? x, object? y) + { + if (x == null) + return y == null ? 0 : -1; + if (y == null) + return 1; + if (!(x is IComparable) || !(y is IComparable)) + throw new ArgumentException(); + return ((IComparable)x).CompareTo(y); + } + + #endregion + + #region Arithmetic + + /// + /// Returns a new that adds the value of the specified to the value of this instance. + /// + /// An NpgsqlTimeSpan interval. + /// An object whose value is the sum of the date and time represented by this instance and the time interval represented by value. + public NpgsqlDateTime Add(in NpgsqlTimeSpan value) => AddTicks(value.UnjustifyInterval().TotalTicks); + + /// + /// Returns a new that adds the value of the specified TimeSpan to the value of this instance. + /// + /// A positive or negative time interval. + /// An object whose value is the sum of the date and time represented by this instance and the time interval represented by value. + public NpgsqlDateTime Add(TimeSpan value) { return AddTicks(value.Ticks); } + + /// + /// Returns a new that adds the specified number of years to the value of this instance. + /// + /// A number of years. The value parameter can be negative or positive. + /// An object whose value is the sum of the date and time represented by this instance and the number of years represented by value. + public NpgsqlDateTime AddYears(int value) + => _type switch + { + InternalType.Infinity => this, + InternalType.NegativeInfinity => this, + _ => new NpgsqlDateTime(_type, _date.AddYears(value), _time) + }; + + /// + /// Returns a new that adds the specified number of months to the value of this instance. + /// + /// A number of months. The months parameter can be negative or positive. + /// An object whose value is the sum of the date and time represented by this instance and months. + public NpgsqlDateTime AddMonths(int value) + => _type switch + { + InternalType.Infinity => this, + InternalType.NegativeInfinity => this, + _ => new NpgsqlDateTime(_type, _date.AddMonths(value), _time) + }; + + /// + /// Returns a new that adds the specified number of days to the value of this instance. + /// + /// A number of whole and fractional days. The value parameter can be negative or positive. + /// An object whose value is the sum of the date and time represented by this instance and the number of days represented by value. + public NpgsqlDateTime AddDays(double value) => Add(TimeSpan.FromDays(value)); + + /// + /// Returns a new that adds the specified number of hours to the value of this instance. + /// + /// A number of whole and fractional hours. The value parameter can be negative or positive. + /// An object whose value is the sum of the date and time represented by this instance and the number of hours represented by value. + public NpgsqlDateTime AddHours(double value) => Add(TimeSpan.FromHours(value)); + + /// + /// Returns a new that adds the specified number of minutes to the value of this instance. + /// + /// A number of whole and fractional minutes. The value parameter can be negative or positive. + /// An object whose value is the sum of the date and time represented by this instance and the number of minutes represented by value. + public NpgsqlDateTime AddMinutes(double value) => Add(TimeSpan.FromMinutes(value)); + + /// + /// Returns a new that adds the specified number of minutes to the value of this instance. + /// + /// A number of whole and fractional minutes. The value parameter can be negative or positive. + /// An object whose value is the sum of the date and time represented by this instance and the number of minutes represented by value. + public NpgsqlDateTime AddSeconds(double value) => Add(TimeSpan.FromSeconds(value)); + + /// + /// Returns a new that adds the specified number of milliseconds to the value of this instance. + /// + /// A number of whole and fractional milliseconds. The value parameter can be negative or positive. Note that this value is rounded to the nearest integer. + /// An object whose value is the sum of the date and time represented by this instance and the number of milliseconds represented by value. + public NpgsqlDateTime AddMilliseconds(double value) => Add(TimeSpan.FromMilliseconds(value)); + + /// + /// Returns a new that adds the specified number of ticks to the value of this instance. + /// + /// A number of 100-nanosecond ticks. The value parameter can be positive or negative. + /// An object whose value is the sum of the date and time represented by this instance and the time represented by value. + public NpgsqlDateTime AddTicks(long value) + => _type switch + { + InternalType.Infinity => this, + InternalType.NegativeInfinity => this, + _ => new NpgsqlDateTime(Ticks + value, Kind), + }; + + public NpgsqlDateTime Subtract(in NpgsqlTimeSpan interval) => Add(-interval); + + public NpgsqlTimeSpan Subtract(NpgsqlDateTime timestamp) + { + switch (_type) { + case InternalType.Infinity: + case InternalType.NegativeInfinity: + throw new InvalidOperationException("You cannot subtract infinity timestamps"); + } + switch (timestamp._type) { + case InternalType.Infinity: + case InternalType.NegativeInfinity: + throw new InvalidOperationException("You cannot subtract infinity timestamps"); + } + return new NpgsqlTimeSpan(0, _date.DaysSinceEra - timestamp._date.DaysSinceEra, _time.Ticks - timestamp._time.Ticks); + } + + #endregion + + #region Operators + + public static NpgsqlDateTime operator +(NpgsqlDateTime timestamp, NpgsqlTimeSpan interval) + => timestamp.Add(interval); + + public static NpgsqlDateTime operator +(NpgsqlTimeSpan interval, NpgsqlDateTime timestamp) + => timestamp.Add(interval); + + public static NpgsqlDateTime operator -(NpgsqlDateTime timestamp, NpgsqlTimeSpan interval) + => timestamp.Subtract(interval); + + public static NpgsqlTimeSpan operator -(NpgsqlDateTime x, NpgsqlDateTime y) => x.Subtract(y); + public static bool operator ==(NpgsqlDateTime x, NpgsqlDateTime y) => x.Equals(y); + public static bool operator !=(NpgsqlDateTime x, NpgsqlDateTime y) => !(x == y); + public static bool operator <(NpgsqlDateTime x, NpgsqlDateTime y) => x.CompareTo(y) < 0; + public static bool operator >(NpgsqlDateTime x, NpgsqlDateTime y) => x.CompareTo(y) > 0; + public static bool operator <=(NpgsqlDateTime x, NpgsqlDateTime y) => x.CompareTo(y) <= 0; + public static bool operator >=(NpgsqlDateTime x, NpgsqlDateTime y) => x.CompareTo(y) >= 0; + + #endregion + + #region Casts + + /// + /// Implicit cast of a to an + /// + /// A + /// An equivalent . + public static implicit operator NpgsqlDateTime(DateTime dateTime) => ToNpgsqlDateTime(dateTime); + public static NpgsqlDateTime ToNpgsqlDateTime(DateTime dateTime) => new(dateTime); + + /// + /// Explicit cast of an to a . + /// + /// An . + /// An equivalent . + public static explicit operator DateTime(NpgsqlDateTime npgsqlDateTime) + => npgsqlDateTime.ToDateTime(); + + #endregion + + public NpgsqlDateTime Normalize() => Add(NpgsqlTimeSpan.Zero); + + static InternalType KindToInternalType(DateTimeKind kind) + => kind switch + { + DateTimeKind.Unspecified => InternalType.FiniteUnspecified, + DateTimeKind.Utc => InternalType.FiniteUtc, + DateTimeKind.Local => InternalType.FiniteLocal, + _ => throw new InvalidOperationException($"Internal Npgsql bug: unexpected value {kind} of enum {nameof(NpgsqlDateTime)}.{nameof(InternalType)}. Please file a bug.") + }; + + enum InternalType + { + FiniteUnspecified, + FiniteUtc, + FiniteLocal, + Infinity, + NegativeInfinity + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlTypes/NpgsqlDbType.cs b/LibExternal/Npgsql/NpgsqlTypes/NpgsqlDbType.cs new file mode 100644 index 0000000..b4c4d8e --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlTypes/NpgsqlDbType.cs @@ -0,0 +1,662 @@ +using System; +using Npgsql; +using Npgsql.TypeMapping; + +#pragma warning disable CA1720 + +// ReSharper disable once CheckNamespace +namespace NpgsqlTypes; + +/// +/// Represents a PostgreSQL data type that can be written or read to the database. +/// Used in places such as to unambiguously specify +/// how to encode or decode values. +/// +/// See https://www.postgresql.org/docs/current/static/datatype.html +public enum NpgsqlDbType +{ + // Note that it's important to never change the numeric values of this enum, since user applications + // compile them in. + + #region Numeric Types + + /// + /// Corresponds to the PostgreSQL 8-byte "bigint" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-numeric.html + [BuiltInPostgresType("int8", PostgresTypeOIDs.Int8)] + Bigint = 1, + + /// + /// Corresponds to the PostgreSQL 8-byte floating-point "double" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-numeric.html + [BuiltInPostgresType("float8", PostgresTypeOIDs.Float8)] + Double = 8, + + /// + /// Corresponds to the PostgreSQL 4-byte "integer" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-numeric.html + [BuiltInPostgresType("int4", PostgresTypeOIDs.Int4)] + Integer = 9, + + /// + /// Corresponds to the PostgreSQL arbitrary-precision "numeric" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-numeric.html + [BuiltInPostgresType("numeric", PostgresTypeOIDs.Numeric)] + Numeric = 13, + + /// + /// Corresponds to the PostgreSQL floating-point "real" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-numeric.html + [BuiltInPostgresType("float4", PostgresTypeOIDs.Float4)] + Real = 17, + + /// + /// Corresponds to the PostgreSQL 2-byte "smallint" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-numeric.html + [BuiltInPostgresType("int2", PostgresTypeOIDs.Int2)] + Smallint = 18, + + /// + /// Corresponds to the PostgreSQL "money" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-money.html + [BuiltInPostgresType("money", PostgresTypeOIDs.Money)] + Money = 12, + + #endregion + + #region Boolean Type + + /// + /// Corresponds to the PostgreSQL "boolean" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-boolean.html + [BuiltInPostgresType("bool", PostgresTypeOIDs.Bool)] + Boolean = 2, + + #endregion + + #region Geometric types + + /// + /// Corresponds to the PostgreSQL geometric "box" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-geometric.html + [BuiltInPostgresType("box", PostgresTypeOIDs.Box)] + Box = 3, + + /// + /// Corresponds to the PostgreSQL geometric "circle" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-geometric.html + [BuiltInPostgresType("circle", PostgresTypeOIDs.Circle)] + Circle = 5, + + /// + /// Corresponds to the PostgreSQL geometric "line" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-geometric.html + [BuiltInPostgresType("line", PostgresTypeOIDs.Line)] + Line = 10, + + /// + /// Corresponds to the PostgreSQL geometric "lseg" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-geometric.html + [BuiltInPostgresType("lseg", PostgresTypeOIDs.LSeg)] + LSeg = 11, + + /// + /// Corresponds to the PostgreSQL geometric "path" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-geometric.html + [BuiltInPostgresType("path", PostgresTypeOIDs.Path)] + Path = 14, + + /// + /// Corresponds to the PostgreSQL geometric "point" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-geometric.html + [BuiltInPostgresType("point", PostgresTypeOIDs.Point)] + Point = 15, + + /// + /// Corresponds to the PostgreSQL geometric "polygon" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-geometric.html + [BuiltInPostgresType("polygon", PostgresTypeOIDs.Polygon)] + Polygon = 16, + + #endregion + + #region Character Types + + /// + /// Corresponds to the PostgreSQL "char(n)" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-character.html + [BuiltInPostgresType("bpchar", PostgresTypeOIDs.BPChar)] + Char = 6, + + /// + /// Corresponds to the PostgreSQL "text" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-character.html + [BuiltInPostgresType("text", PostgresTypeOIDs.Text)] + Text = 19, + + /// + /// Corresponds to the PostgreSQL "varchar" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-character.html + [BuiltInPostgresType("varchar", PostgresTypeOIDs.Varchar)] + Varchar = 22, + + /// + /// Corresponds to the PostgreSQL internal "name" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-character.html + [BuiltInPostgresType("name", PostgresTypeOIDs.Name)] + Name = 32, + + /// + /// Corresponds to the PostgreSQL "citext" type for the citext module. + /// + /// See https://www.postgresql.org/docs/current/static/citext.html + Citext = 51, // Extension type + + /// + /// Corresponds to the PostgreSQL "char" type. + /// + /// + /// This is an internal field and should normally not be used for regular applications. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-text.html + /// + [BuiltInPostgresType("char", PostgresTypeOIDs.Char)] + InternalChar = 38, + + #endregion + + #region Binary Data Types + + /// + /// Corresponds to the PostgreSQL "bytea" type, holding a raw byte string. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-binary.html + [BuiltInPostgresType("bytea", PostgresTypeOIDs.Bytea)] + Bytea = 4, + + #endregion + + #region Date/Time Types + + /// + /// Corresponds to the PostgreSQL "date" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-datetime.html + [BuiltInPostgresType("date", PostgresTypeOIDs.Date)] + Date = 7, + + /// + /// Corresponds to the PostgreSQL "time" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-datetime.html + [BuiltInPostgresType("time", PostgresTypeOIDs.Time)] + Time = 20, + + /// + /// Corresponds to the PostgreSQL "timestamp" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-datetime.html + [BuiltInPostgresType("timestamp", PostgresTypeOIDs.Timestamp)] + Timestamp = 21, + + /// + /// Corresponds to the PostgreSQL "timestamp with time zone" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-datetime.html + [Obsolete("Use TimestampTz instead")] // NOTE: Don't remove this (see #1694) + TimestampTZ = TimestampTz, + + /// + /// Corresponds to the PostgreSQL "timestamp with time zone" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-datetime.html + [BuiltInPostgresType("timestamptz", PostgresTypeOIDs.TimestampTz)] + TimestampTz = 26, + + /// + /// Corresponds to the PostgreSQL "interval" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-datetime.html + [BuiltInPostgresType("interval", PostgresTypeOIDs.Interval)] + Interval = 30, + + /// + /// Corresponds to the PostgreSQL "time with time zone" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-datetime.html + [Obsolete("Use TimeTz instead")] // NOTE: Don't remove this (see #1694) + TimeTZ = TimeTz, + + /// + /// Corresponds to the PostgreSQL "time with time zone" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-datetime.html + [BuiltInPostgresType("timetz", PostgresTypeOIDs.TimeTz)] + TimeTz = 31, + + /// + /// Corresponds to the obsolete PostgreSQL "abstime" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-datetime.html + [Obsolete("The PostgreSQL abstime time is obsolete.")] + [BuiltInPostgresType("abstime", PostgresTypeOIDs.Abstime)] + Abstime = 33, + + #endregion + + #region Network Address Types + + /// + /// Corresponds to the PostgreSQL "inet" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-net-types.html + [BuiltInPostgresType("inet", PostgresTypeOIDs.Inet)] + Inet = 24, + + /// + /// Corresponds to the PostgreSQL "cidr" type, a field storing an IPv4 or IPv6 network. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-net-types.html + [BuiltInPostgresType("cidr", PostgresTypeOIDs.Cidr)] + Cidr = 44, + + /// + /// Corresponds to the PostgreSQL "macaddr" type, a field storing a 6-byte physical address. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-net-types.html + [BuiltInPostgresType("macaddr", PostgresTypeOIDs.Macaddr)] + MacAddr = 34, + + /// + /// Corresponds to the PostgreSQL "macaddr8" type, a field storing a 6-byte or 8-byte physical address. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-net-types.html + [BuiltInPostgresType("macaddr8", PostgresTypeOIDs.Macaddr8)] + MacAddr8 = 54, + + #endregion + + #region Bit String Types + + /// + /// Corresponds to the PostgreSQL "bit" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-bit.html + [BuiltInPostgresType("bit", PostgresTypeOIDs.Bit)] + Bit = 25, + + /// + /// Corresponds to the PostgreSQL "varbit" type, a field storing a variable-length string of bits. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-boolean.html + [BuiltInPostgresType("varbit", PostgresTypeOIDs.Varbit)] + Varbit = 39, + + #endregion + + #region Text Search Types + + /// + /// Corresponds to the PostgreSQL "tsvector" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-textsearch.html + [BuiltInPostgresType("tsvector", PostgresTypeOIDs.TsVector)] + TsVector = 45, + + /// + /// Corresponds to the PostgreSQL "tsquery" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-textsearch.html + [BuiltInPostgresType("tsquery", PostgresTypeOIDs.TsQuery)] + TsQuery = 46, + + /// + /// Corresponds to the PostgreSQL "regconfig" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-textsearch.html + [BuiltInPostgresType("regconfig", PostgresTypeOIDs.Regconfig)] + Regconfig = 56, + + #endregion + + #region UUID Type + + /// + /// Corresponds to the PostgreSQL "uuid" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-uuid.html + [BuiltInPostgresType("uuid", PostgresTypeOIDs.Uuid)] + Uuid = 27, + + #endregion + + #region XML Type + + /// + /// Corresponds to the PostgreSQL "xml" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-xml.html + [BuiltInPostgresType("xml", PostgresTypeOIDs.Xml)] + Xml = 28, + + #endregion + + #region JSON Types + + /// + /// Corresponds to the PostgreSQL "json" type, a field storing JSON in text format. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-json.html + /// + [BuiltInPostgresType("json", PostgresTypeOIDs.Json)] + Json = 35, + + /// + /// Corresponds to the PostgreSQL "jsonb" type, a field storing JSON in an optimized binary. + /// format. + /// + /// + /// Supported since PostgreSQL 9.4. + /// See https://www.postgresql.org/docs/current/static/datatype-json.html + /// + [BuiltInPostgresType("jsonb", PostgresTypeOIDs.Jsonb)] + Jsonb = 36, + + /// + /// Corresponds to the PostgreSQL "jsonpath" type, a field storing JSON path in text format. + /// format. + /// + /// + /// Supported since PostgreSQL 12. + /// See https://www.postgresql.org/docs/current/datatype-json.html#DATATYPE-JSONPATH + /// + [BuiltInPostgresType("jsonpath", PostgresTypeOIDs.JsonPath)] + JsonPath = 57, + + #endregion + + #region HSTORE Type + + /// + /// Corresponds to the PostgreSQL "hstore" type, a dictionary of string key-value pairs. + /// + /// See https://www.postgresql.org/docs/current/static/hstore.html + Hstore = 37, // Extension type + + #endregion + + #region Internal Types + + /// + /// Corresponds to the PostgreSQL "refcursor" type. + /// + [BuiltInPostgresType("refcursor", PostgresTypeOIDs.Refcursor)] + Refcursor = 23, + + /// + /// Corresponds to the PostgreSQL internal "oidvector" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-oid.html + [BuiltInPostgresType("oidvector", PostgresTypeOIDs.Oidvector)] + Oidvector = 29, + + /// + /// Corresponds to the PostgreSQL internal "int2vector" type. + /// + [BuiltInPostgresType("int2vector", PostgresTypeOIDs.Int2vector)] + Int2Vector = 52, + + /// + /// Corresponds to the PostgreSQL "oid" type. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-oid.html + [BuiltInPostgresType("oid", PostgresTypeOIDs.Oid)] + Oid = 41, + + /// + /// Corresponds to the PostgreSQL "xid" type, an internal transaction identifier. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-oid.html + [BuiltInPostgresType("xid", PostgresTypeOIDs.Xid)] + Xid = 42, + + /// + /// Corresponds to the PostgreSQL "xid8" type, an internal transaction identifier. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-oid.html + [BuiltInPostgresType("xid8", PostgresTypeOIDs.Xid8)] + Xid8 = 64, + + /// + /// Corresponds to the PostgreSQL "cid" type, an internal command identifier. + /// + /// See https://www.postgresql.org/docs/current/static/datatype-oid.html + [BuiltInPostgresType("cid", PostgresTypeOIDs.Cid)] + Cid = 43, + + /// + /// Corresponds to the PostgreSQL "regtype" type, a numeric (OID) ID of a type in the pg_type table. + /// + [BuiltInPostgresType("regtype", PostgresTypeOIDs.Regtype)] + Regtype = 49, + + /// + /// Corresponds to the PostgreSQL "tid" type, a tuple id identifying the physical location of a row within its table. + /// + [BuiltInPostgresType("tid", PostgresTypeOIDs.Tid)] + Tid = 53, + + /// + /// Corresponds to the PostgreSQL "pg_lsn" type, which can be used to store LSN (Log Sequence Number) data which + /// is a pointer to a location in the WAL. + /// + /// + /// See: https://www.postgresql.org/docs/current/datatype-pg-lsn.html and + /// https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=7d03a83f4d0736ba869fa6f93973f7623a27038a + /// + [BuiltInPostgresType("pg_lsn", PostgresTypeOIDs.PgLsn)] + PgLsn = 59, + + #endregion + + #region Special + + /// + /// A special value that can be used to send parameter values to the database without + /// specifying their type, allowing the database to cast them to another value based on context. + /// The value will be converted to a string and send as text. + /// + /// + /// This value shouldn't ordinarily be used, and makes sense only when sending a data type + /// unsupported by Npgsql. + /// + [BuiltInPostgresType("unknown", PostgresTypeOIDs.Unknown)] + Unknown = 40, + + #endregion + + #region PostGIS + + /// + /// The geometry type for PostgreSQL spatial extension PostGIS. + /// + Geometry = 50, // Extension type + + /// + /// The geography (geodetic) type for PostgreSQL spatial extension PostGIS. + /// + Geography = 55, // Extension type + + #endregion + + #region Label tree types + + /// + /// The PostgreSQL ltree type, each value is a label path "a.label.tree.value", forming a tree in a set. + /// + /// See http://www.postgresql.org/docs/current/static/ltree.html + LTree = 60, // Extension type + + /// + /// The PostgreSQL lquery type for PostgreSQL extension ltree + /// + /// See http://www.postgresql.org/docs/current/static/ltree.html + LQuery = 61, // Extension type + + /// + /// The PostgreSQL ltxtquery type for PostgreSQL extension ltree + /// + /// See http://www.postgresql.org/docs/current/static/ltree.html + LTxtQuery = 62, // Extension type + + #endregion + + #region Range types + + /// + /// Corresponds to the PostgreSQL "int4range" type. + /// + [BuiltInPostgresType("int4range", PostgresTypeOIDs.Int4Range)] + IntegerRange = Range | Integer, + + /// + /// Corresponds to the PostgreSQL "int8range" type. + /// + [BuiltInPostgresType("int8range", PostgresTypeOIDs.Int8Range)] + BigIntRange = Range | Bigint, + + /// + /// Corresponds to the PostgreSQL "numrange" type. + /// + [BuiltInPostgresType("numrange", PostgresTypeOIDs.NumRange)] + NumericRange = Range | Numeric, + + /// + /// Corresponds to the PostgreSQL "tsrange" type. + /// + [BuiltInPostgresType("tsrange", PostgresTypeOIDs.TsRange)] + TimestampRange = Range | Timestamp, + + /// + /// Corresponds to the PostgreSQL "tstzrange" type. + /// + [BuiltInPostgresType("tstzrange", PostgresTypeOIDs.TsTzRange)] + TimestampTzRange = Range | TimestampTz, + + /// + /// Corresponds to the PostgreSQL "daterange" type. + /// + [BuiltInPostgresType("daterange", PostgresTypeOIDs.DateRange)] + DateRange = Range | Date, + + #endregion Range types + + #region Multirange types + + /// + /// Corresponds to the PostgreSQL "int4multirange" type. + /// + [BuiltInPostgresType("int4multirange", PostgresTypeOIDs.Int4Multirange)] + IntegerMultirange = Multirange | Integer, + + /// + /// Corresponds to the PostgreSQL "int8multirange" type. + /// + [BuiltInPostgresType("int8multirange", PostgresTypeOIDs.Int8Multirange)] + BigIntMultirange = Multirange | Bigint, + + /// + /// Corresponds to the PostgreSQL "nummultirange" type. + /// + [BuiltInPostgresType("nummultirange", PostgresTypeOIDs.NumMultirange)] + NumericMultirange = Multirange | Numeric, + + /// + /// Corresponds to the PostgreSQL "tsmultirange" type. + /// + [BuiltInPostgresType("tsmultirange", PostgresTypeOIDs.TsMultirange)] + TimestampMultirange = Multirange | Timestamp, + + /// + /// Corresponds to the PostgreSQL "tstzmultirange" type. + /// + [BuiltInPostgresType("tstzmultirange", PostgresTypeOIDs.TsTzMultirange)] + TimestampTzMultirange = Multirange | TimestampTz, + + /// + /// Corresponds to the PostgreSQL "datemultirange" type. + /// + [BuiltInPostgresType("datemultirange", PostgresTypeOIDs.DateMultirange)] + DateMultirange = Multirange | Date, + + #endregion Multirange types + + #region Composables + + /// + /// Corresponds to the PostgreSQL "array" type, a variable-length multidimensional array of + /// another type. This value must be combined with another value from + /// via a bit OR (e.g. NpgsqlDbType.Array | NpgsqlDbType.Integer) + /// + /// See https://www.postgresql.org/docs/current/static/arrays.html + Array = int.MinValue, + + /// + /// Corresponds to the PostgreSQL "range" type, continuous range of values of specific type. + /// This value must be combined with another value from + /// via a bit OR (e.g. NpgsqlDbType.Range | NpgsqlDbType.Integer) + /// + /// + /// Supported since PostgreSQL 9.2. + /// See https://www.postgresql.org/docs/current/static/rangetypes.html + /// + Range = 0x40000000, + + /// + /// Corresponds to the PostgreSQL "multirange" type, continuous range of values of specific type. + /// This value must be combined with another value from + /// via a bit OR (e.g. NpgsqlDbType.Multirange | NpgsqlDbType.Integer) + /// + /// + /// Supported since PostgreSQL 14. + /// See https://www.postgresql.org/docs/current/static/rangetypes.html + /// + Multirange = 0x20000000, + + #endregion +} + +/// +/// Represents a built-in PostgreSQL type as it appears in pg_type, including its name and OID. +/// Extension types with variable OIDs are not represented. +/// +class BuiltInPostgresType : Attribute +{ + internal string Name { get; } + internal uint OID { get; } + + internal BuiltInPostgresType(string name, uint oid) + { + Name = name; + OID = oid; + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlTypes/NpgsqlInterval.cs b/LibExternal/Npgsql/NpgsqlTypes/NpgsqlInterval.cs new file mode 100644 index 0000000..f3c1d49 --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlTypes/NpgsqlInterval.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using Npgsql; + +// ReSharper disable once CheckNamespace +namespace NpgsqlTypes; + +/// +/// A raw representation of the PostgreSQL interval datatype. Use only when or NodaTime +/// Period do not have sufficient range to handle your values. +/// +/// +///

+/// See https://www.postgresql.org/docs/current/static/datatype-geometric.html. +///

+///

+/// Do not use this type unless you have to: prefer or NodaTime +/// Period when possible. +///

+///
+public readonly struct NpgsqlInterval : IEquatable +{ + /// + /// Constructs an . + /// + public NpgsqlInterval(int months, int days, long time) + => (Months, Days, Time) = (months, days, time); + + /// + /// Months and years, after time for alignment. + /// + public int Months { get; } + + /// + /// Days, after time for alignment. + /// + public int Days { get; } + + /// + /// Remaining time unit smaller than a day, in microseconds. + /// + public long Time { get; } + + /// + public bool Equals(NpgsqlInterval other) + => Months == other.Months && Days == other.Days && Time == other.Time; + + /// + public override bool Equals(object? obj) + => obj is NpgsqlInterval other && Equals(other); + + /// + public override int GetHashCode() + => HashCode.Combine(Months, Days, Time); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlTypes/NpgsqlLogSequenceNumber.cs b/LibExternal/Npgsql/NpgsqlTypes/NpgsqlLogSequenceNumber.cs new file mode 100644 index 0000000..e770627 --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlTypes/NpgsqlLogSequenceNumber.cs @@ -0,0 +1,340 @@ +using System; +using System.Globalization; + +// ReSharper disable once CheckNamespace +namespace NpgsqlTypes; + +/// +/// Wraps a PostgreSQL Write-Ahead Log Sequence Number (see: https://www.postgresql.org/docs/current/datatype-pg-lsn.html) +/// +/// +/// Log Sequence Numbers are a fundamental concept of the PostgreSQL Write-Ahead Log and by that of +/// PostgreSQL replication. See https://www.postgresql.org/docs/current/wal-internals.html for what they represent. +/// +/// This struct provides conversions from/to and and beyond that tries to port +/// the methods and operators in https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/adt/pg_lsn.c +/// but nothing more. +/// +public readonly struct NpgsqlLogSequenceNumber : IEquatable, IComparable +{ + /// + /// Zero is used indicate an invalid Log Sequence Number. No XLOG record can begin at zero. + /// + public static readonly NpgsqlLogSequenceNumber Invalid = default; + + readonly ulong _value; + + /// + /// Initializes a new instance of . + /// + /// The value to wrap. + public NpgsqlLogSequenceNumber(ulong value) + => _value = value; + + /// + /// Returns a value indicating whether this instance is equal to a specified + /// instance. + /// + /// A instance to compare to this instance. + /// if the current instance is equal to the value parameter; + /// otherwise, . + public bool Equals(NpgsqlLogSequenceNumber other) + => _value == other._value; + + /// + /// Compares this instance to a specified and returns an indication of their + /// relative values. + /// + /// A instance to compare to this instance. + /// A signed number indicating the relative values of this instance and . + public int CompareTo(NpgsqlLogSequenceNumber value) + => _value.CompareTo(value._value); + + /// + /// Returns a value indicating whether this instance is equal to a specified object. + /// + /// An object to compare to this instance + /// if the current instance is equal to the value parameter; + /// otherwise, . + public override bool Equals(object? obj) + => obj is NpgsqlLogSequenceNumber lsn && lsn._value == _value; + + /// + /// Returns the hash code for this instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + => _value.GetHashCode(); + + /// + /// Converts the numeric value of this instance to its equivalent string representation. + /// + /// The string representation of the value of this instance, consisting of two hexadecimal numbers of + /// up to 8 digits each, separated by a slash + public override string ToString() + => unchecked($"{(uint)(_value >> 32):X}/{(uint)_value:X}"); + + /// + /// Converts the string representation of a Log Sequence Number to a instance. + /// + /// A string that represents the Log Sequence Number to convert. + /// + /// A equivalent to the Log Sequence Number specified in . + /// + /// The parameter is . + /// + /// The parameter represents a number less than or greater than + /// . + /// + /// The parameter is not in the right format. + public static NpgsqlLogSequenceNumber Parse(string s) + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + => s is null + ? throw new ArgumentNullException(nameof(s)) + : Parse(s.AsSpan()); + + /// + /// Converts the span representation of a Log Sequence Number to a instance. + /// + /// A span containing the characters that represent the Log Sequence Number to convert. + /// + /// A equivalent to the Log Sequence Number specified in . + /// + /// + /// The parameter represents a number less than or greater than + /// . + /// + /// The parameter is not in the right format. + public static NpgsqlLogSequenceNumber Parse(ReadOnlySpan s) + => TryParse(s, out var parsed) + ? parsed + : throw new FormatException($"Invalid Log Sequence Number: '{s.ToString()}'."); + + /// + /// Tries to convert the string representation of a Log Sequence Number to an + /// instance. A return value indicates whether the conversion succeeded or failed. + /// + /// A string that represents the Log Sequence Number to convert. + /// + /// When this method returns, contains a instance equivalent to the Log Sequence + /// Number contained in , if the conversion succeeded, or the default value for + /// (0) if the conversion failed. The conversion fails if the + /// parameter is or , is not in the right format, or represents a number + /// less than or greater than . This parameter is + /// passed uninitialized; any value originally supplied in result will be overwritten. + /// + /// + /// if c> was converted successfully; otherwise, . + /// + public static bool TryParse(string s, out NpgsqlLogSequenceNumber result) + => TryParse(s.AsSpan(), out result); + + /// + /// Tries to convert the span representation of a Log Sequence Number to an + /// instance. A return value indicates whether the conversion succeeded or failed. + /// + /// A span containing the characters that represent the Log Sequence Number to convert. + /// + /// When this method returns, contains a instance equivalent to the Log Sequence + /// Number contained in , if the conversion succeeded, or the default value for + /// (0) if the conversion failed. The conversion fails if the + /// parameter is empty, is not in the right format, or represents a number less than + /// or greater than . This parameter is passed + /// uninitialized; any value originally supplied in result will be overwritten. + /// + /// + /// if was converted successfully; otherwise, . + public static bool TryParse(ReadOnlySpan s, out NpgsqlLogSequenceNumber result) + { + for (var i = 0; i < s.Length; i++) + { + if (s[i] != '/') continue; + +#if NETSTANDARD2_0 + var firstPart = s.Slice(0, i).ToString(); + var secondPart = s.Slice(++i).ToString(); +#else + var firstPart = s.Slice(0, i); + var secondPart = s.Slice(++i); +#endif + + if (!uint.TryParse(firstPart, NumberStyles.AllowHexSpecifier, null, out var first)) + { + result = default; + return false; + } + if (!uint.TryParse(secondPart, NumberStyles.AllowHexSpecifier, null, out var second)) + { + result = default; + return false; + } + result = new NpgsqlLogSequenceNumber(((ulong)first << 32) + second); + return true; + } + result = default; + return false; + } + + /// + /// Converts the value of a 64-bit unsigned integer to a instance. + /// + /// A 64-bit unsigned integer. + /// A new instance of initialized to . + public static explicit operator NpgsqlLogSequenceNumber(ulong value) + => new(value); + + /// + /// Converts the value of a instance to a 64-bit unsigned integer value. + /// + /// A instance + /// The contents of as 64-bit unsigned integer. + public static explicit operator ulong(NpgsqlLogSequenceNumber value) + => value._value; + + /// + /// Returns a value that indicates whether two specified instances of are equal. + /// + /// The first Log Sequence Number to compare. + /// The second Log Sequence Number to compare. + /// + /// if equals ; otherwise, . + /// + public static bool operator ==(NpgsqlLogSequenceNumber value1, NpgsqlLogSequenceNumber value2) + => value1._value == value2._value; + + /// + /// Returns a value that indicates whether two specified instances of are not + /// equal. + /// + /// The first Log Sequence Number to compare. + /// The second Log Sequence Number to compare. + /// + /// if does not equal ; otherwise, + /// . + /// + public static bool operator !=(NpgsqlLogSequenceNumber value1, NpgsqlLogSequenceNumber value2) + => value1._value != value2._value; + + /// + /// Returns a value indicating whether a specified instance is greater than + /// another specified instance. + /// + /// The first value to compare. + /// The second value to compare. + /// + /// if is greater than ; otherwise, + /// . + /// + public static bool operator >(NpgsqlLogSequenceNumber value1, NpgsqlLogSequenceNumber value2) + => value1._value > value2._value; + + /// + /// Returns a value indicating whether a specified instance is less than + /// another specified instance. + /// + /// The first value to compare. + /// The second value to compare. + /// + /// if is less than ; otherwise, + /// . + /// + public static bool operator <(NpgsqlLogSequenceNumber value1, NpgsqlLogSequenceNumber value2) + => value1._value < value2._value; + + /// + /// Returns a value indicating whether a specified instance is greater than or + /// equal to another specified instance. + /// + /// The first value to compare. + /// The second value to compare. + /// + /// if is greater than or equal to ; + /// otherwise, . + /// + public static bool operator >=(NpgsqlLogSequenceNumber value1, NpgsqlLogSequenceNumber value2) + => value1._value >= value2._value; + + /// + /// Returns the larger of two values. + /// + /// The first value to compare. + /// The second value to compare. + /// + /// The larger of the two values. + /// + public static NpgsqlLogSequenceNumber Larger(NpgsqlLogSequenceNumber value1, NpgsqlLogSequenceNumber value2) + => value1._value > value2._value ? value1 : value2; + + /// + /// Returns the smaller of two values. + /// + /// The first value to compare. + /// The second value to compare. + /// + /// The smaller of the two values. + /// + public static NpgsqlLogSequenceNumber Smaller(NpgsqlLogSequenceNumber value1, NpgsqlLogSequenceNumber value2) + => value1._value < value2._value ? value1 : value2; + + /// + /// Returns a value indicating whether a specified instance is less than or + /// equal to another specified instance. + /// + /// The first value to compare. + /// The second value to compare. + /// + /// if is less than or equal to ; + /// otherwise, . + /// + public static bool operator <=(NpgsqlLogSequenceNumber value1, NpgsqlLogSequenceNumber value2) + => value1._value <= value2._value; + + /// + /// Subtracts two specified values. + /// + /// The first value. + /// The second value. + /// The number of bytes separating those write-ahead log locations. + public static ulong operator -(NpgsqlLogSequenceNumber first, NpgsqlLogSequenceNumber second) + => first._value < second._value + ? second._value - first._value + : first._value - second._value; + + /// + /// Subtract the number of bytes from a instance, giving a new + /// instance. + /// Handles both positive and negative numbers of bytes. + /// + /// + /// The instance representing a write-ahead log location. + /// + /// The number of bytes to subtract. + /// A new instance. + /// + /// The resulting instance would represent a number less than + /// . + /// + public static NpgsqlLogSequenceNumber operator -(NpgsqlLogSequenceNumber lsn, double nbytes) + => double.IsNaN(nbytes) || double.IsInfinity(nbytes) + ? throw new NotFiniteNumberException($"Cannot subtract {nbytes} from {nameof(NpgsqlLogSequenceNumber)}", nbytes) + : new NpgsqlLogSequenceNumber(checked((ulong)(lsn._value - nbytes))); + + /// + /// Add the number of bytes to a instance, giving a new + /// instance. + /// Handles both positive and negative numbers of bytes. + /// + /// + /// The instance representing a write-ahead log location. + /// + /// The number of bytes to add. + /// A new instance. + /// + /// The resulting instance would represent a number greater than + /// . + /// + public static NpgsqlLogSequenceNumber operator +(NpgsqlLogSequenceNumber lsn, double nbytes) + => double.IsNaN(nbytes) || double.IsInfinity(nbytes) + ? throw new NotFiniteNumberException($"Cannot add {nbytes} to {nameof(NpgsqlLogSequenceNumber)}", nbytes) + : new NpgsqlLogSequenceNumber(checked((ulong)(lsn._value + nbytes))); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlTypes/NpgsqlRange.cs b/LibExternal/Npgsql/NpgsqlTypes/NpgsqlRange.cs new file mode 100644 index 0000000..9672052 --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlTypes/NpgsqlRange.cs @@ -0,0 +1,527 @@ +using System; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Text; + +// ReSharper disable once CheckNamespace +namespace NpgsqlTypes; + +/// +/// Represents a PostgreSQL range type. +/// +/// The element type of the values in the range. +/// +/// See: https://www.postgresql.org/docs/current/static/rangetypes.html +/// +public readonly struct NpgsqlRange : IEquatable> +{ + // ----------------------------------------------------------------------------------------------- + // Regarding bitwise flag checks via @roji: + // + // > Note that Flags.HasFlag() used to be very inefficient compared to simply doing the + // > bit operation - this is why I've always avoided it. .NET Core 2.1 adds JIT intrinstics + // > for this, making Enum.HasFlag() fast, but I honestly don't see the value over just doing + // > a bitwise and operation, which would also be fast under .NET Core 2.0 and .NET Framework. + // + // See: + // - https://github.com/npgsql/npgsql/pull/1939#pullrequestreview-121308396 + // - https://blogs.msdn.microsoft.com/dotnet/2018/04/18/performance-improvements-in-net-core-2-1 + // ----------------------------------------------------------------------------------------------- + + /// + /// Defined by PostgreSQL to represent an empty range. + /// + const string EmptyLiteral = "empty"; + + /// + /// Defined by PostgreSQL to represent an infinite lower bound. + /// Some element types may have specific handling for this value distinct from a missing or null value. + /// + const string LowerInfinityLiteral = "-infinity"; + + /// + /// Defined by PostgreSQL to represent an infinite upper bound. + /// Some element types may have specific handling for this value distinct from a missing or null value. + /// + const string UpperInfinityLiteral = "infinity"; + + /// + /// Defined by PostgreSQL to represent an null bound. + /// Some element types may have specific handling for this value distinct from an infinite or missing value. + /// + const string NullLiteral = "null"; + + /// + /// Defined by PostgreSQL to represent a lower inclusive bound. + /// + const char LowerInclusiveBound = '['; + + /// + /// Defined by PostgreSQL to represent a lower exclusive bound. + /// + const char LowerExclusiveBound = '('; + + /// + /// Defined by PostgreSQL to represent an upper inclusive bound. + /// + const char UpperInclusiveBound = ']'; + + /// + /// Defined by PostgreSQL to represent an upper exclusive bound. + /// + const char UpperExclusiveBound = ')'; + + /// + /// Defined by PostgreSQL to separate the values for the upper and lower bounds. + /// + const char BoundSeparator = ','; + + /// + /// The used by to convert bounds into . + /// + static readonly TypeConverter BoundConverter = TypeDescriptor.GetConverter(typeof(T)); + + /// + /// True if implements ; otherwise, false. + /// + static readonly bool HasEquatableBounds = typeof(IEquatable).IsAssignableFrom(typeof(T)); + + /// + /// Represents the empty range. This field is read-only. + /// + public static readonly NpgsqlRange Empty = new(default, default, RangeFlags.Empty); + + /// + /// The lower bound of the range. Only valid when is false. + /// + [MaybeNull, AllowNull] + public T LowerBound { get; } + + /// + /// The upper bound of the range. Only valid when is false. + /// + [MaybeNull, AllowNull] + public T UpperBound { get; } + + /// + /// The characteristics of the boundaries. + /// + internal readonly RangeFlags Flags; + + /// + /// True if the lower bound is part of the range (i.e. inclusive); otherwise, false. + /// + public bool LowerBoundIsInclusive => (Flags & RangeFlags.LowerBoundInclusive) != 0; + + /// + /// True if the upper bound is part of the range (i.e. inclusive); otherwise, false. + /// + public bool UpperBoundIsInclusive => (Flags & RangeFlags.UpperBoundInclusive) != 0; + + /// + /// True if the lower bound is indefinite (i.e. infinite or unbounded); otherwise, false. + /// + public bool LowerBoundInfinite => (Flags & RangeFlags.LowerBoundInfinite) != 0; + + /// + /// True if the upper bound is indefinite (i.e. infinite or unbounded); otherwise, false. + /// + public bool UpperBoundInfinite => (Flags & RangeFlags.UpperBoundInfinite) != 0; + + /// + /// True if the range is empty; otherwise, false. + /// + public bool IsEmpty => (Flags & RangeFlags.Empty) != 0; + + /// + /// Constructs an with inclusive and definite bounds. + /// + /// The lower bound of the range. + /// The upper bound of the range. + public NpgsqlRange([AllowNull] T lowerBound, [AllowNull] T upperBound) + : this(lowerBound, true, false, upperBound, true, false) { } + + /// + /// Constructs an with definite bounds. + /// + /// The lower bound of the range. + /// True if the lower bound is is part of the range (i.e. inclusive); otherwise, false. + /// The upper bound of the range. + /// True if the upper bound is part of the range (i.e. inclusive); otherwise, false. + public NpgsqlRange( + [AllowNull] T lowerBound, bool lowerBoundIsInclusive, + [AllowNull] T upperBound, bool upperBoundIsInclusive) + : this(lowerBound, lowerBoundIsInclusive, false, upperBound, upperBoundIsInclusive, false) { } + + /// + /// Constructs an . + /// + /// The lower bound of the range. + /// True if the lower bound is is part of the range (i.e. inclusive); otherwise, false. + /// True if the lower bound is indefinite (i.e. infinite or unbounded); otherwise, false. + /// The upper bound of the range. + /// True if the upper bound is part of the range (i.e. inclusive); otherwise, false. + /// True if the upper bound is indefinite (i.e. infinite or unbounded); otherwise, false. + public NpgsqlRange( + [AllowNull] T lowerBound, bool lowerBoundIsInclusive, bool lowerBoundInfinite, + [AllowNull] T upperBound, bool upperBoundIsInclusive, bool upperBoundInfinite) + : this( + lowerBound, + upperBound, + EvaluateBoundaryFlags( + lowerBoundIsInclusive, + upperBoundIsInclusive, + lowerBoundInfinite, + upperBoundInfinite)) { } + + /// + /// Constructs an . + /// + /// The lower bound of the range. + /// The upper bound of the range. + /// The characteristics of the range boundaries. + internal NpgsqlRange([AllowNull] T lowerBound, [AllowNull] T upperBound, RangeFlags flags) : this() + { + // TODO: We need to check if the bounds are implicitly empty. E.g. '(1,1)' or '(0,0]'. + // See: https://github.com/npgsql/npgsql/issues/1943. + + LowerBound = (flags & RangeFlags.LowerBoundInfinite) != 0 ? default : lowerBound; + UpperBound = (flags & RangeFlags.UpperBoundInfinite) != 0 ? default : upperBound; + Flags = flags; + + if (IsEmptyRange(LowerBound, UpperBound, Flags)) + { + LowerBound = default!; + UpperBound = default!; + Flags = RangeFlags.Empty; + } + } + + /// + /// Attempts to determine if the range is malformed or implicitly empty. + /// + /// The lower bound of the range. + /// The upper bound of the range. + /// The characteristics of the range boundaries. + /// + /// True if the range is implicitly empty; otherwise, false. + /// + static bool IsEmptyRange([AllowNull] T lowerBound, [AllowNull] T upperBound, RangeFlags flags) + { + // --------------------------------------------------------------------------------- + // We only want to check for those conditions that are unambiguously erroneous: + // 1. The bounds must not be default values (including null). + // 2. The bounds must be definite (non-infinite). + // 3. The bounds must be inclusive. + // 4. The bounds must be considered equal. + // + // See: + // - https://github.com/npgsql/npgsql/pull/1939 + // - https://github.com/npgsql/npgsql/issues/1943 + // --------------------------------------------------------------------------------- + + if ((flags & RangeFlags.Empty) == RangeFlags.Empty) + return true; + + if ((flags & RangeFlags.Infinite) == RangeFlags.Infinite) + return false; + + if ((flags & RangeFlags.Inclusive) == RangeFlags.Inclusive) + return false; + + if (lowerBound is null || upperBound is null) + return false; + + if (!HasEquatableBounds) + return lowerBound?.Equals(upperBound) ?? false; + + var lower = (IEquatable)lowerBound; + var upper = (IEquatable)upperBound; + + return !lower.Equals(default!) && !upper.Equals(default!) && lower.Equals(upperBound); + } + + /// + /// Evaluates the boundary flags. + /// + /// True if the lower bound is is part of the range (i.e. inclusive); otherwise, false. + /// True if the lower bound is indefinite (i.e. infinite or unbounded); otherwise, false. + /// True if the upper bound is part of the range (i.e. inclusive); otherwise, false. + /// True if the upper bound is indefinite (i.e. infinite or unbounded); otherwise, false. + /// + /// The boundary characteristics. + /// + static RangeFlags EvaluateBoundaryFlags(bool lowerBoundIsInclusive, bool upperBoundIsInclusive, bool lowerBoundInfinite, bool upperBoundInfinite) + { + var result = RangeFlags.None; + + // This is the only place flags are calculated. + if (lowerBoundIsInclusive) + result |= RangeFlags.LowerBoundInclusive; + if (upperBoundIsInclusive) + result |= RangeFlags.UpperBoundInclusive; + if (lowerBoundInfinite) + result |= RangeFlags.LowerBoundInfinite; + if (upperBoundInfinite) + result |= RangeFlags.UpperBoundInfinite; + + // PostgreSQL automatically converts inclusive-infinities. + // See: https://www.postgresql.org/docs/current/static/rangetypes.html#RANGETYPES-INFINITE + if ((result & RangeFlags.LowerInclusiveInfinite) == RangeFlags.LowerInclusiveInfinite) + result &= ~RangeFlags.LowerBoundInclusive; + + if ((result & RangeFlags.UpperInclusiveInfinite) == RangeFlags.UpperInclusiveInfinite) + result &= ~RangeFlags.UpperBoundInclusive; + + return result; + } + + /// + /// Indicates whether the on the left is equal to the on the right. + /// + /// The on the left. + /// The on the right. + /// + /// True if the on the left is equal to the on the right; otherwise, false. + /// + public static bool operator ==(NpgsqlRange x, NpgsqlRange y) => x.Equals(y); + + /// + /// Indicates whether the on the left is not equal to the on the right. + /// + /// The on the left. + /// The on the right. + /// + /// True if the on the left is not equal to the on the right; otherwise, false. + /// + public static bool operator !=(NpgsqlRange x, NpgsqlRange y) => !x.Equals(y); + + /// + public override bool Equals(object? o) => o is NpgsqlRange range && Equals(range); + + /// + public bool Equals(NpgsqlRange other) + { + if (Flags != other.Flags) + return false; + + if (HasEquatableBounds) + { + var lowerEqual = LowerBound is null + ? other.LowerBound is null + : !(other.LowerBound is null) && ((IEquatable)LowerBound).Equals(other.LowerBound); + + if (!lowerEqual) + return false; + + return UpperBound is null + ? other.UpperBound is null + : !(other.UpperBound is null) && ((IEquatable)UpperBound).Equals(other.UpperBound); + } + + return + (LowerBound?.Equals(other.LowerBound) ?? other.LowerBound is null) && + (UpperBound?.Equals(other.UpperBound) ?? other.UpperBound is null); + } + + /// + public override int GetHashCode() + => unchecked((397 * (int)Flags) ^ (397 * (LowerBound?.GetHashCode() ?? 0)) ^ (397 * (UpperBound?.GetHashCode() ?? 0))); + + /// + public override string ToString() + { + if (IsEmpty) + return EmptyLiteral; + + var sb = new StringBuilder(); + + sb.Append(LowerBoundIsInclusive ? LowerInclusiveBound : LowerExclusiveBound); + + if (!LowerBoundInfinite) + sb.Append(LowerBound); + + sb.Append(BoundSeparator); + + if (!UpperBoundInfinite) + sb.Append(UpperBound); + + sb.Append(UpperBoundIsInclusive ? UpperInclusiveBound : UpperExclusiveBound); + + return sb.ToString(); + } + + // TODO: rewrite this to use ReadOnlySpan for the 4.1 release + /// + /// Parses the well-known text representation of a PostgreSQL range type into a . + /// + /// A PosgreSQL range type in a well-known text format. + /// + /// The represented by the . + /// + /// + /// Malformed range literal. + /// + /// + /// Malformed range literal. Missing left parenthesis or bracket. + /// + /// + /// Malformed range literal. Missing right parenthesis or bracket. + /// + /// + /// Malformed range literal. Missing comma after lower bound. + /// + /// + /// See: https://www.postgresql.org/docs/current/static/rangetypes.html + /// + public static NpgsqlRange Parse(string value) + { + if (value is null) + throw new ArgumentNullException(nameof(value)); + + value = value.Trim(); + + if (value.Length < 3) + throw new FormatException("Malformed range literal."); + + if (string.Equals(value, EmptyLiteral, StringComparison.OrdinalIgnoreCase)) + return Empty; + + var lowerInclusive = value[0] == LowerInclusiveBound; + var lowerExclusive = value[0] == LowerExclusiveBound; + + if (!lowerInclusive && !lowerExclusive) + throw new FormatException("Malformed range literal. Missing left parenthesis or bracket."); + + var upperInclusive = value[value.Length - 1] == UpperInclusiveBound; + var upperExclusive = value[value.Length - 1] == UpperExclusiveBound; + + if (!upperInclusive && !upperExclusive) + throw new FormatException("Malformed range literal. Missing right parenthesis or bracket."); + + var separator = value.IndexOf(BoundSeparator); + + if (separator == -1) + throw new FormatException("Malformed range literal. Missing comma after lower bound."); + + if (separator != value.LastIndexOf(BoundSeparator)) + // TODO: this should be replaced to handle quoted commas. + throw new NotSupportedException("Ranges with embedded commas are not currently supported."); + + // Skip the opening bracket and stop short of the separator. + var lowerSegment = value.Substring(1, separator - 1).Trim(); + + // Skip past the separator and stop short of the closing bracket. + var upperSegment = value.Substring(separator + 1, value.Length - separator - 2).Trim(); + + // TODO: infinity literals have special meaning to some types (e.g. daterange), we should consider a flag to track them. + + var lowerInfinite = + lowerSegment.Length == 0 || + string.Equals(lowerSegment, string.Empty, StringComparison.OrdinalIgnoreCase) || + string.Equals(lowerSegment, NullLiteral, StringComparison.OrdinalIgnoreCase) || + string.Equals(lowerSegment, LowerInfinityLiteral, StringComparison.OrdinalIgnoreCase); + + var upperInfinite = + upperSegment.Length == 0 || + string.Equals(upperSegment, string.Empty, StringComparison.OrdinalIgnoreCase) || + string.Equals(upperSegment, NullLiteral, StringComparison.OrdinalIgnoreCase) || + string.Equals(upperSegment, UpperInfinityLiteral, StringComparison.OrdinalIgnoreCase); + + var lower = lowerInfinite ? default : (T?)BoundConverter.ConvertFromString(lowerSegment); + var upper = upperInfinite ? default : (T?)BoundConverter.ConvertFromString(upperSegment); + + return new NpgsqlRange(lower, lowerInclusive, lowerInfinite, upper, upperInclusive, upperInfinite); + } + + /// + /// Represents a type converter for . + /// + public class RangeTypeConverter : TypeConverter + { + /// + /// Adds a to the closed form . + /// + public static void Register() => + TypeDescriptor.AddAttributes( + typeof(NpgsqlRange), + new TypeConverterAttribute(typeof(RangeTypeConverter))); + + /// + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) + => sourceType == typeof(string); + + /// + public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType) + => destinationType == typeof(string); + + /// + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + => value is string s ? Parse(s) : base.ConvertFrom(context, culture, value); + + /// + public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) + => value is null ? string.Empty : value.ToString(); + } +} + +/// +/// Represents characteristics of range type boundaries. +/// +/// +/// See: https://www.postgresql.org/docs/current/static/rangetypes.html +/// +[Flags] +enum RangeFlags : byte +{ + /// + /// The default flag. The range is not empty and has boundaries that are definite and exclusive. + /// + None = 0, + + /// + /// The range is empty. E.g. '(0,0)', 'empty'. + /// + Empty = 1, + + /// + /// The lower bound is inclusive. E.g. '[0,5]', '[0,5)', '[0,)'. + /// + LowerBoundInclusive = 2, + + /// + /// The upper bound is inclusive. E.g. '[0,5]', '(0,5]', '(,5]'. + /// + UpperBoundInclusive = 4, + + /// + /// The lower bound is infinite or indefinite. E.g. '(null,5]', '(-infinity,5]', '(,5]'. + /// + LowerBoundInfinite = 8, + + /// + /// The upper bound is infinite or indefinite. E.g. '[0,null)', '[0,infinity)', '[0,)'. + /// + UpperBoundInfinite = 16, + + /// + /// Both the lower and upper bounds are inclusive. + /// + Inclusive = LowerBoundInclusive | UpperBoundInclusive, + + /// + /// Both the lower and upper bounds are indefinite. + /// + Infinite = LowerBoundInfinite | UpperBoundInfinite, + + /// + /// The lower bound is both inclusive and indefinite. This represents an error condition. + /// + LowerInclusiveInfinite = LowerBoundInclusive | LowerBoundInfinite, + + /// + /// The upper bound is both inclusive and indefinite. This represents an error condition. + /// + UpperInclusiveInfinite = UpperBoundInclusive | UpperBoundInfinite +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlTypes/NpgsqlTimeSpan.cs b/LibExternal/Npgsql/NpgsqlTypes/NpgsqlTimeSpan.cs new file mode 100644 index 0000000..8539145 --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlTypes/NpgsqlTimeSpan.cs @@ -0,0 +1,909 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using Npgsql; + +// ReSharper disable once CheckNamespace +namespace NpgsqlTypes; + +/// +/// Represents the PostgreSQL interval datatype. +/// +/// PostgreSQL differs from .NET in how it's interval type doesn't assume 24 hours in a day +/// (to deal with 23- and 25-hour days caused by daylight savings adjustments) and has a concept +/// of months that doesn't exist in .NET's class. (Neither datatype +/// has any concessions for leap-seconds). +/// For most uses just casting to and from TimeSpan will work correctly — in particular, +/// the results of subtracting one or the PostgreSQL date, time and +/// timestamp types from another should be the same whether you do so in .NET or PostgreSQL — +/// but if the handling of days and months in PostgreSQL is important to your application then you +/// should use this class instead of . +/// If you don't know whether these differences are important to your application, they +/// probably arent! Just use and do not use this class directly ☺ +/// To avoid forcing unnecessary provider-specific concerns on users who need not be concerned +/// with them a call to on a field containing an +/// value will return a rather than an +/// . If you need the extra functionality of +/// then use . +/// +/// +/// +/// +/// +[Obsolete( + "For values outside the range of TimeSpan, consider using NodaTime (range -9998 to 9999), or read the value as an NpgsqlInterval. " + + "See https://www.npgsql.org/doc/types/datetime.html for more information.")] +[Serializable] +public readonly struct NpgsqlTimeSpan : IComparable, IComparer, IEquatable, IComparable, + IComparer +{ + #region Constants + + /// + /// Represents the number of ticks (100ns periods) in one microsecond. This field is constant. + /// + public const long TicksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000; + + /// + /// Represents the number of ticks (100ns periods) in one millisecond. This field is constant. + /// + public const long TicksPerMillsecond = TimeSpan.TicksPerMillisecond; + + /// + /// Represents the number of ticks (100ns periods) in one second. This field is constant. + /// + public const long TicksPerSecond = TimeSpan.TicksPerSecond; + + /// + /// Represents the number of ticks (100ns periods) in one minute. This field is constant. + /// + public const long TicksPerMinute = TimeSpan.TicksPerMinute; + + /// + /// Represents the number of ticks (100ns periods) in one hour. This field is constant. + /// + public const long TicksPerHour = TimeSpan.TicksPerHour; + + /// + /// Represents the number of ticks (100ns periods) in one day. This field is constant. + /// + public const long TicksPerDay = TimeSpan.TicksPerDay; + + /// + /// Represents the number of hours in one day (assuming no daylight savings adjustments). This field is constant. + /// + public const int HoursPerDay = 24; + + /// + /// Represents the number of days assumed in one month if month justification or unjustifcation is performed. + /// This is set to 30 for consistency with PostgreSQL. Note that this is means that month adjustments cause + /// a year to be taken as 30 × 12 = 360 rather than 356/366 days. + /// + public const int DaysPerMonth = 30; + + /// + /// Represents the number of ticks (100ns periods) in one day, assuming 30 days per month. + /// + public const long TicksPerMonth = TicksPerDay * DaysPerMonth; + + /// + /// Represents the number of months in a year. This field is constant. + /// + public const int MonthsPerYear = 12; + + /// + /// Represents the maximum . This field is read-only. + /// + public static readonly NpgsqlTimeSpan MaxValue = new(long.MaxValue); + + /// + /// Represents the minimum . This field is read-only. + /// + public static readonly NpgsqlTimeSpan MinValue = new(long.MinValue); + + /// + /// Represents the zero . This field is read-only. + /// + public static readonly NpgsqlTimeSpan Zero = new(0); + + #endregion + + readonly int _months; + readonly int _days; + readonly long _ticks; + + #region Constructors + + /// + /// Initializes a new to the specified number of ticks. + /// + /// A time period expressed in 100ns units. + public NpgsqlTimeSpan(long ticks) + : this(new TimeSpan(ticks)) + { + } + + /// + /// Initializes a new to hold the same time as a + /// + /// A time period expressed in a + public NpgsqlTimeSpan(TimeSpan timespan) + : this(0, timespan.Days, timespan.Ticks - (TicksPerDay * timespan.Days)) + { + } + + /// + /// Initializes a new to the specified number of months, days + /// & ticks. + /// + /// Number of months. + /// Number of days. + /// Number of 100ns units. + public NpgsqlTimeSpan(int months, int days, long ticks) + { + _months = months; + _days = days; + _ticks = ticks; + } + + /// + /// Initializes a new to the specified number of + /// days, hours, minutes & seconds. + /// + /// Number of days. + /// Number of hours. + /// Number of minutes. + /// Number of seconds. + public NpgsqlTimeSpan(int days, int hours, int minutes, int seconds) + : this(0, days, new TimeSpan(hours, minutes, seconds).Ticks) + { + } + + /// + /// Initializes a new to the specified number of + /// days, hours, minutes, seconds & milliseconds. + /// + /// Number of days. + /// Number of hours. + /// Number of minutes. + /// Number of seconds. + /// Number of milliseconds. + public NpgsqlTimeSpan(int days, int hours, int minutes, int seconds, int milliseconds) + : this(0, days, new TimeSpan(0, hours, minutes, seconds, milliseconds).Ticks) + { + } + + /// + /// Initializes a new to the specified number of + /// months, days, hours, minutes, seconds & milliseconds. + /// + /// Number of months. + /// Number of days. + /// Number of hours. + /// Number of minutes. + /// Number of seconds. + /// Number of milliseconds. + public NpgsqlTimeSpan(int months, int days, int hours, int minutes, int seconds, int milliseconds) + : this(months, days, new TimeSpan(0, hours, minutes, seconds, milliseconds).Ticks) + { + } + + /// + /// Initializes a new to the specified number of + /// years, months, days, hours, minutes, seconds & milliseconds. + /// Years are calculated exactly equivalent to 12 months. + /// + /// Number of years. + /// Number of months. + /// Number of days. + /// Number of hours. + /// Number of minutes. + /// Number of seconds. + /// Number of milliseconds. + public NpgsqlTimeSpan(int years, int months, int days, int hours, int minutes, int seconds, int milliseconds) + : this(years * 12 + months, days, new TimeSpan(0, hours, minutes, seconds, milliseconds).Ticks) + { + } + + #endregion + + #region Whole Parts + + /// + /// The total number of ticks(100ns units) contained. This is the resolution of the + /// type. This ignores the number of days and + /// months held. If you want them included use first. + /// The resolution of the PostgreSQL + /// interval type is by default 1µs = 1,000 ns. It may be smaller as follows: + /// + /// + /// interval(0) + /// resolution of 1s (1 second) + /// + /// + /// interval(1) + /// resolution of 100ms = 0.1s (100 milliseconds) + /// + /// + /// interval(2) + /// resolution of 10ms = 0.01s (10 milliseconds) + /// + /// + /// interval(3) + /// resolution of 1ms = 0.001s (1 millisecond) + /// + /// + /// interval(4) + /// resolution of 100µs = 0.0001s (100 microseconds) + /// + /// + /// interval(5) + /// resolution of 10µs = 0.00001s (10 microseconds) + /// + /// + /// interval(6) or interval + /// resolution of 1µs = 0.000001s (1 microsecond) + /// + /// + /// As such, if the 100-nanosecond resolution is significant to an application, a PostgreSQL interval will + /// not suffice for those purposes. + /// In more frequent cases though, the resolution of the interval suffices. + /// will always suffice to handle the resolution of any interval value, and upon + /// writing to the database, will be rounded to the resolution used. + /// + /// The number of ticks in the instance. + /// + public long Ticks => _ticks; + + /// + /// Gets the number of whole microseconds held in the instance. + /// An in the range [-999999, 999999]. + /// + public int Microseconds => (int)((_ticks / 10) % 1000000); + + /// + /// Gets the number of whole milliseconds held in the instance. + /// An in the range [-999, 999]. + /// + public int Milliseconds => (int)((_ticks / TicksPerMillsecond) % 1000); + + /// + /// Gets the number of whole seconds held in the instance. + /// An in the range [-59, 59]. + /// + public int Seconds => (int)((_ticks / TicksPerSecond) % 60); + + /// + /// Gets the number of whole minutes held in the instance. + /// An in the range [-59, 59]. + /// + public int Minutes => (int)((_ticks / TicksPerMinute) % 60); + + /// + /// Gets the number of whole hours held in the instance. + /// Note that this can be less than -23 or greater than 23 unless + /// has been used to produce this instance. + /// + public int Hours => (int)(_ticks / TicksPerHour); + + /// + /// Gets the number of days held in the instance. + /// Note that this does not pay attention to a time component with -24 or less hours or + /// 24 or more hours, unless has been called to produce this instance. + /// + public int Days => _days; + + /// + /// Gets the number of months held in the instance. + /// Note that this does not pay attention to a day component with -30 or less days or + /// 30 or more days, unless has been called to produce this instance. + /// + public int Months => _months; + + /// + /// Returns a representing the time component of the instance. + /// Note that this may have a value beyond the range ±23:59:59.9999999 unless + /// has been called to produce this instance. + /// + public TimeSpan Time => new(_ticks); + + #endregion + + #region Total Parts + + /// + /// The total number of ticks (100ns units) in the instance, assuming 24 hours in each day and + /// 30 days in a month. + /// + public long TotalTicks => Ticks + Days * TicksPerDay + Months * TicksPerMonth; + + /// + /// The total number of microseconds in the instance, assuming 24 hours in each day and + /// 30 days in a month. + /// + public double TotalMicroseconds => TotalTicks / 10d; + + /// + /// The total number of milliseconds in the instance, assuming 24 hours in each day and + /// 30 days in a month. + /// + public double TotalMilliseconds => TotalTicks / (double)TicksPerMillsecond; + + /// + /// The total number of seconds in the instance, assuming 24 hours in each day and + /// 30 days in a month. + /// + public double TotalSeconds => TotalTicks / (double)TicksPerSecond; + + /// + /// The total number of minutes in the instance, assuming 24 hours in each day and + /// 30 days in a month. + /// + public double TotalMinutes => TotalTicks / (double)TicksPerMinute; + + /// + /// The total number of hours in the instance, assuming 24 hours in each day and + /// 30 days in a month. + /// + public double TotalHours => TotalTicks / (double)TicksPerHour; + + /// + /// The total number of days in the instance, assuming 24 hours in each day and + /// 30 days in a month. + /// + public double TotalDays => TotalTicks / (double)TicksPerDay; + + /// + /// The total number of months in the instance, assuming 24 hours in each day and + /// 30 days in a month. + /// + public double TotalMonths => TotalTicks / (double)TicksPerMonth; + + #endregion + + #region Create From Part + + /// + /// Creates an from a number of ticks. + /// + /// The number of ticks (100ns units) in the interval. + /// A d with the given number of ticks. + public static NpgsqlTimeSpan FromTicks(long ticks) => new NpgsqlTimeSpan(ticks).Canonicalize(); + + /// + /// Creates an from a number of microseconds. + /// + /// The number of microseconds in the interval. + /// A d with the given number of microseconds. + public static NpgsqlTimeSpan FromMicroseconds(double micro) => FromTicks((long)(micro * TicksPerMicrosecond)); + + /// + /// Creates an from a number of milliseconds. + /// + /// The number of milliseconds in the interval. + /// A d with the given number of milliseconds. + public static NpgsqlTimeSpan FromMilliseconds(double milli) => FromTicks((long)(milli * TicksPerMillsecond)); + + /// + /// Creates an from a number of seconds. + /// + /// The number of seconds in the interval. + /// A d with the given number of seconds. + public static NpgsqlTimeSpan FromSeconds(double seconds) => FromTicks((long)(seconds * TicksPerSecond)); + + /// + /// Creates an from a number of minutes. + /// + /// The number of minutes in the interval. + /// A d with the given number of minutes. + public static NpgsqlTimeSpan FromMinutes(double minutes) => FromTicks((long)(minutes * TicksPerMinute)); + + /// + /// Creates an from a number of hours. + /// + /// The number of hours in the interval. + /// A d with the given number of hours. + public static NpgsqlTimeSpan FromHours(double hours) => FromTicks((long)(hours * TicksPerHour)); + + /// + /// Creates an from a number of days. + /// + /// The number of days in the interval. + /// A d with the given number of days. + public static NpgsqlTimeSpan FromDays(double days) => FromTicks((long)(days * TicksPerDay)); + + /// + /// Creates an from a number of months. + /// + /// The number of months in the interval. + /// A d with the given number of months. + public static NpgsqlTimeSpan FromMonths(double months) => FromTicks((long)(months * TicksPerMonth)); + + #endregion + + #region Arithmetic + + /// + /// Adds another interval to this instance and returns the result. + /// + /// An to add to this instance. + /// An whose values are the sums of the two instances. + public NpgsqlTimeSpan Add(in NpgsqlTimeSpan interval) + => new(Months + interval.Months, Days + interval.Days, Ticks + interval.Ticks); + + /// + /// Subtracts another interval from this instance and returns the result. + /// + /// An to subtract from this instance. + /// An whose values are the differences of the two instances. + public NpgsqlTimeSpan Subtract(in NpgsqlTimeSpan interval) + => new(Months - interval.Months, Days - interval.Days, Ticks - interval.Ticks); + + /// + /// Returns an whose value is the negated value of this instance. + /// + /// An whose value is the negated value of this instance. + public NpgsqlTimeSpan Negate() => new(-Months, -Days, -Ticks); + + /// + /// This absolute value of this instance. In the case of some, but not all, components being negative, + /// the rules used for justification are used to determine if the instance is positive or negative. + /// + /// An whose value is the absolute value of this instance. + public NpgsqlTimeSpan Duration() + => UnjustifyInterval().Ticks < 0 ? Negate() : this; + + #endregion + + #region Justification + + /// + /// Equivalent to PostgreSQL's justify_days function. + /// + /// An based on this one, but with any hours outside of the range [-23, 23] + /// converted into days. + public NpgsqlTimeSpan JustifyDays() + { + return new(Months, Days + (int)(Ticks / TicksPerDay), Ticks % TicksPerDay); + } + + /// + /// Opposite to PostgreSQL's justify_days function. + /// + /// An based on this one, but with any days converted to multiples of ±24hours. + public NpgsqlTimeSpan UnjustifyDays() + { + return new(Months, 0, Ticks + Days * TicksPerDay); + } + + /// + /// Equivalent to PostgreSQL's justify_months function. + /// + /// An based on this one, but with any days outside of the range [-30, 30] + /// converted into months. + public NpgsqlTimeSpan JustifyMonths() + { + return new(Months + Days / DaysPerMonth, Days % DaysPerMonth, Ticks); + } + + /// + /// Opposite to PostgreSQL's justify_months function. + /// + /// An based on this one, but with any months converted to multiples of ±30days. + public NpgsqlTimeSpan UnjustifyMonths() + { + return new(0, Days + Months * DaysPerMonth, Ticks); + } + + /// + /// Equivalent to PostgreSQL's justify_interval function. + /// + /// An based on this one, + /// but with any months converted to multiples of ±30days + /// and then with any days converted to multiples of ±24hours + public NpgsqlTimeSpan JustifyInterval() + { + return JustifyMonths().JustifyDays(); + } + + /// + /// Opposite to PostgreSQL's justify_interval function. + /// + /// An based on this one, but with any months converted to multiples of ±30days and then any days converted to multiples of ±24hours; + public NpgsqlTimeSpan UnjustifyInterval() + { + return new(Ticks + Days * TicksPerDay + Months * DaysPerMonth * TicksPerDay); + } + + /// + /// Produces a canonical NpgslInterval with 0 months and hours in the range of [-23, 23]. + /// + /// + /// While the fact that for many purposes, two different instances could be considered + /// equivalent (e.g. one with 2days, 3hours and one with 1day 27hours) there are different possible canonical forms. + /// + /// E.g. we could move all excess hours into days and all excess days into months and have the most readable form, + /// or we could move everything into the ticks and have the form that allows for the easiest arithmetic) the form + /// chosen has two important properties that make it the best choice. + /// First, it is closest two how + /// objects are most often represented. Second, it is compatible with results of many + /// PostgreSQL functions, particularly with age() and the results of subtracting one date, time or timestamp from + /// another. + /// + /// Note that the results of casting a to is + /// canonicalised. + /// + /// + /// An based on this one, but with months converted to multiples of ±30days and with any hours outside of the range [-23, 23] + /// converted into days. + public NpgsqlTimeSpan Canonicalize() + { + return new(0, Days + Months * DaysPerMonth + (int)(Ticks / TicksPerDay), Ticks % TicksPerDay); + } + + #endregion + + #region Casts + + /// + /// Implicit cast of a to an + /// + /// A + /// An eqivalent, canonical, . + public static implicit operator NpgsqlTimeSpan(TimeSpan timespan) => ToNpgsqlTimeSpan(timespan); + + /// + /// Casts a to an . + /// + public static NpgsqlTimeSpan ToNpgsqlTimeSpan(TimeSpan timespan) => new NpgsqlTimeSpan(timespan).Canonicalize(); + + /// + /// Explicit cast of an to a . + /// + /// A . + /// An equivalent . + public static explicit operator TimeSpan(NpgsqlTimeSpan interval) + => ToTimeSpan(interval); + + /// + /// Casts an to a . + /// + public static TimeSpan ToTimeSpan(in NpgsqlTimeSpan interval) + => interval.Months > 0 + ? throw new InvalidCastException("Cannot convert interval value with non-zero months to TimeSpan") + : new(interval.Ticks + interval.Days * TicksPerDay); + + #endregion + + #region Comparison + + /// + /// Returns true if another is exactly the same as this instance. + /// + /// An for comparison. + /// true if the two instances are exactly the same, + /// false otherwise. + public bool Equals(NpgsqlTimeSpan other) + => Ticks == other.Ticks && Days == other.Days && Months == other.Months; + + /// + /// Returns true if another object is an , that is exactly the same as + /// this instance + /// + /// An for comparison. + /// true if the argument is an and is exactly the same + /// as this one, false otherwise. + public override bool Equals(object? obj) => obj is NpgsqlTimeSpan span && Equals(span); + + /// + /// Compares two instances. + /// + /// The first . + /// The second . + /// 0 if the two are equal or equivalent. A value greater than zero if x is greater than y, + /// a value less than zero if x is less than y. + public static int Compare(NpgsqlTimeSpan x, NpgsqlTimeSpan y) => x.CompareTo(y); + + int IComparer.Compare(NpgsqlTimeSpan x, NpgsqlTimeSpan y) => x.CompareTo(y); + + int IComparer.Compare(object? x, object? y) + { + if (x == null) + return y == null ? 0 : 1; + if (y == null) + return -1; + try { + return ((IComparable)x).CompareTo(y); + } catch (Exception) { + throw new ArgumentException(); + } + } + + /// + /// A hash code suitable for uses with hashing algorithms. + /// + /// An signed integer. + public override int GetHashCode() => UnjustifyInterval().Ticks.GetHashCode(); + + /// + /// Compares this instance with another/ + /// + /// An to compare this with. + /// 0 if the instances are equal or equivalent. A value less than zero if + /// this instance is less than the argument. A value greater than zero if this instance + /// is greater than the instance. + public int CompareTo(NpgsqlTimeSpan other) + => UnjustifyInterval().Ticks.CompareTo(other.UnjustifyInterval().Ticks); + + /// + /// Compares this instance with another/ + /// + /// An object to compare this with. + /// 0 if the argument is an and the instances are equal or equivalent. + /// A value less than zero if the argument is an and + /// this instance is less than the argument. + /// A value greater than zero if the argument is an and this instance + /// is greater than the instance. + /// A value greater than zero if the argument is null. + /// The argument is not an . + public int CompareTo(object? other) + { + if (other == null) + return 1; + if (other is NpgsqlTimeSpan) + return CompareTo((NpgsqlTimeSpan)other); + throw new ArgumentException(nameof(other)); + } + + #endregion + + #region String Conversions + + /// + /// Parses a and returns a instance. + /// Designed to use the formats generally returned by PostgreSQL. + /// + /// The to parse. + /// An represented by the argument. + /// The string was null. + /// A value obtained from parsing the string exceeded the values allowed for the relevant component. + /// The string was not in a format that could be parsed to produce an . + public static NpgsqlTimeSpan Parse(string str) + { + if (str == null) { + throw new ArgumentNullException(nameof(str)); + } + str = str.Replace('s', ' '); //Quick and easy way to catch plurals. + try { + var years = 0; + var months = 0; + var days = 0; + var hours = 0; + var minutes = 0; + var seconds = 0m; + var idx = str.IndexOf("year", StringComparison.Ordinal); + if (idx > 0) { + years = int.Parse(str.Substring(0, idx)); + str = SafeSubstring(str, idx + 5); + } + idx = str.IndexOf("mon", StringComparison.Ordinal); + if (idx > 0) { + months = int.Parse(str.Substring(0, idx)); + str = SafeSubstring(str, idx + 4); + } + idx = str.IndexOf("day", StringComparison.Ordinal); + if (idx > 0) { + days = int.Parse(str.Substring(0, idx)); + str = SafeSubstring(str, idx + 4).Trim(); + } + if (str.Length > 0) { + var isNegative = str[0] == '-'; + var parts = str.Split(':'); + switch (parts.Length) //One of those times that fall-through would actually be good. + { + case 1: + hours = int.Parse(parts[0]); + break; + case 2: + hours = int.Parse(parts[0]); + minutes = int.Parse(parts[1]); + break; + default: + hours = int.Parse(parts[0]); + minutes = int.Parse(parts[1]); + seconds = decimal.Parse(parts[2], System.Globalization.CultureInfo.InvariantCulture.NumberFormat); + break; + } + if (isNegative) { + minutes *= -1; + seconds *= -1; + } + } + var ticks = hours * TicksPerHour + minutes * TicksPerMinute + (long)(seconds * TicksPerSecond); + return new NpgsqlTimeSpan(years * MonthsPerYear + months, days, ticks); + } catch (OverflowException) { + throw; + } catch (Exception) { + throw new FormatException(); + } + } + + private static string SafeSubstring(string s, int startIndex) + { + if (startIndex >= s.Length) + return string.Empty; + else + return s.Substring(startIndex); + } + + /// + /// Attempt to parse a to produce an . + /// + /// The to parse. + /// (out) The produced, or if the parsing failed. + /// true if the parsing succeeded, false otherwise. + public static bool TryParse(string str, out NpgsqlTimeSpan result) + { + try { + result = Parse(str); + return true; + } catch (Exception) { + result = Zero; + return false; + } + } + + /// + /// Create a representation of the instance. + /// The format returned is of the form: + /// [M mon[s]] [d day[s]] [HH:mm:ss[.f[f[f[f[f[f[f[f[f]]]]]]]]]] + /// A zero is represented as 00:00:00 + /// + /// Ticks are 100ns, Postgress resolution is only to 1µs at most. Hence we lose 1 or more decimal + /// precision in storing values in the database. Despite this, this method will output that extra + /// digit of precision. It's forward-compatible with any future increases in resolution up to 100ns, + /// and also makes this ToString() more applicable to any other use-case. + /// + /// + /// The representation. + public override string ToString() + { + var sb = new StringBuilder(); + if (Months != 0) { + sb.Append(Months).Append(Math.Abs(Months) == 1 ? " mon " : " mons "); + } + if (Days != 0) { + if (Months < 0 && Days > 0) { + sb.Append('+'); + } + sb.Append(Days).Append(Math.Abs(Days) == 1 ? " day " : " days "); + } + if (Ticks != 0 || sb.Length == 0) { + if (Ticks < 0) { + sb.Append('-'); + } else if (Days < 0 || (Days == 0 && Months < 0)) { + sb.Append('+'); + } + // calculate total seconds and then subtract total whole minutes in seconds to get just the seconds and fractional part + var seconds = _ticks / (decimal)TicksPerSecond - (_ticks / TicksPerMinute) * 60; + sb.Append(Math.Abs(Hours).ToString("D2")).Append(':').Append(Math.Abs(Minutes).ToString("D2")).Append(':').Append(Math.Abs(seconds).ToString("0#.######", System.Globalization.CultureInfo.InvariantCulture.NumberFormat)); + + } + if (sb[sb.Length - 1] == ' ') { + sb.Remove(sb.Length - 1, 1); + } + return sb.ToString(); + } + + #endregion + + #region Common Operators + + /// + /// Adds two together. + /// + /// The first to add. + /// The second to add. + /// An whose values are the sum of the arguments. + public static NpgsqlTimeSpan operator +(NpgsqlTimeSpan x, NpgsqlTimeSpan y) + { + return x.Add(y); + } + + /// + /// Subtracts one from another. + /// + /// The to subtract the other from. + /// The to subtract from the other. + /// An whose values are the difference of the arguments + public static NpgsqlTimeSpan operator -(NpgsqlTimeSpan x, NpgsqlTimeSpan y) + { + return x.Subtract(y); + } + + /// + /// Returns true if two are exactly the same. + /// + /// The first to compare. + /// The second to compare. + /// true if the two arguments are exactly the same, false otherwise. + public static bool operator ==(NpgsqlTimeSpan x, NpgsqlTimeSpan y) + { + return x.Equals(y); + } + + /// + /// Returns false if two are exactly the same. + /// + /// The first to compare. + /// The second to compare. + /// false if the two arguments are exactly the same, true otherwise. + public static bool operator !=(NpgsqlTimeSpan x, NpgsqlTimeSpan y) + { + return !(x == y); + } + + /// + /// Compares two instances to see if the first is less than the second + /// + /// The first to compare. + /// The second to compare. + /// true if the first is less than second, false otherwise. + public static bool operator <(NpgsqlTimeSpan x, NpgsqlTimeSpan y) + { + return x.UnjustifyInterval().Ticks < y.UnjustifyInterval().Ticks; + } + + /// + /// Compares two instances to see if the first is less than or equivalent to the second + /// + /// The first to compare. + /// The second to compare. + /// true if the first is less than or equivalent to second, false otherwise. + public static bool operator <=(NpgsqlTimeSpan x, NpgsqlTimeSpan y) + { + return x.UnjustifyInterval().Ticks <= y.UnjustifyInterval().Ticks; + } + + /// + /// Compares two instances to see if the first is greater than the second + /// + /// The first to compare. + /// The second to compare. + /// true if the first is greater than second, false otherwise. + public static bool operator >(NpgsqlTimeSpan x, NpgsqlTimeSpan y) + { + return !(x <= y); + } + + /// + /// Compares two instances to see if the first is greater than or equivalent the second + /// + /// The first to compare. + /// The second to compare. + /// true if the first is greater than or equivalent to the second, false otherwise. + public static bool operator >=(NpgsqlTimeSpan x, NpgsqlTimeSpan y) + { + return !(x < y); + } + + /// + /// Returns the instance. + /// + public static NpgsqlTimeSpan operator +(NpgsqlTimeSpan x) => Plus(x); + + /// + /// Returns the instance. + /// + public static NpgsqlTimeSpan Plus(in NpgsqlTimeSpan x) => x; + + /// + /// Negates an instance. + /// + /// An . + /// The negation of the argument. + public static NpgsqlTimeSpan operator -(NpgsqlTimeSpan x) => x.Negate(); + + #endregion +} \ No newline at end of file diff --git a/LibExternal/Npgsql/NpgsqlTypes/NpgsqlTsQuery.cs b/LibExternal/Npgsql/NpgsqlTypes/NpgsqlTsQuery.cs new file mode 100644 index 0000000..0ef5fc4 --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlTypes/NpgsqlTsQuery.cs @@ -0,0 +1,741 @@ +using System; +using System.Collections.Generic; +using System.Text; + +#pragma warning disable CA1034 + +// ReSharper disable once CheckNamespace +namespace NpgsqlTypes; + +/// +/// Represents a PostgreSQL tsquery. This is the base class for the +/// lexeme, not, or, and, and "followed by" nodes. +/// +public abstract class NpgsqlTsQuery : IEquatable +{ + /// + /// Node kind + /// + public NodeKind Kind { get; } + + /// + /// NodeKind + /// + public enum NodeKind + { + /// + /// Represents the empty tsquery. Should only be used at top level. + /// + Empty = -1, + /// + /// Lexeme + /// + Lexeme = 0, + /// + /// Not operator + /// + Not = 1, + /// + /// And operator + /// + And = 2, + /// + /// Or operator + /// + Or = 3, + /// + /// "Followed by" operator + /// + Phrase = 4 + } + + /// + /// Constructs an . + /// + /// + protected NpgsqlTsQuery(NodeKind kind) => Kind = kind; + + /// + /// Writes the tsquery in PostgreSQL's text format. + /// + public void Write(StringBuilder stringBuilder) => WriteCore(stringBuilder, true); + + internal abstract void WriteCore(StringBuilder sb, bool first = false); + + /// + /// Writes the tsquery in PostgreSQL's text format. + /// + public override string ToString() + { + var sb = new StringBuilder(); + Write(sb); + return sb.ToString(); + } + + /// + /// Parses a tsquery in PostgreSQL's text format. + /// + /// + /// + public static NpgsqlTsQuery Parse(string value) + { + if (value == null) + throw new ArgumentNullException(nameof(value)); + + var valStack = new Stack(); + var opStack = new Stack(); + + var sb = new StringBuilder(); + var pos = 0; + var expectingBinOp = false; + + var lastFollowedByOpDistance = -1; + + NextToken: + if (pos >= value.Length) + goto Finish; + var ch = value[pos++]; + if (ch == '\'') + goto WaitEndComplex; + if ((ch == ')' || ch == '|' || ch == '&') && !expectingBinOp || (ch == '(' || ch == '!') && expectingBinOp) + throw new FormatException("Syntax error in tsquery. Unexpected token."); + + if (ch == '<') + { + var endOfOperatorConsumed = false; + var sbCurrentLength = sb.Length; + + while (pos < value.Length) + { + var c = value[pos++]; + if (c == '>') + { + endOfOperatorConsumed = true; + break; + } + + sb.Append(c); + } + + if (sb.Length == sbCurrentLength || !endOfOperatorConsumed) + throw new FormatException("Syntax error in tsquery. Malformed 'followed by' operator."); + + var followedByOpDistanceString = sb.ToString(sbCurrentLength, sb.Length - sbCurrentLength); + if (followedByOpDistanceString == "-") + { + lastFollowedByOpDistance = 1; + } + else if (!int.TryParse(followedByOpDistanceString, out lastFollowedByOpDistance) + || lastFollowedByOpDistance < 0) + { + throw new FormatException("Syntax error in tsquery. Malformed distance in 'followed by' operator."); + } + + sb.Length -= followedByOpDistanceString.Length; + } + + if (ch == '(' || ch == '!' || ch == '&' || ch == '<') + { + opStack.Push(new NpgsqlTsQueryOperator(ch, lastFollowedByOpDistance)); + expectingBinOp = false; + lastFollowedByOpDistance = 0; + goto NextToken; + } + + if (ch == '|') + { + if (opStack.Count > 0 && opStack.Peek() == '|') + { + if (valStack.Count < 2) + throw new FormatException("Syntax error in tsquery"); + var right = valStack.Pop(); + var left = valStack.Pop(); + valStack.Push(new NpgsqlTsQueryOr(left, right)); + // Implicit pop and repush | + } + else + opStack.Push('|'); + expectingBinOp = false; + goto NextToken; + } + + if (ch == ')') + { + while (opStack.Count > 0 && opStack.Peek() != '(') + { + if (valStack.Count < 2 || opStack.Peek() == '!') + throw new FormatException("Syntax error in tsquery"); + + var right = valStack.Pop(); + var left = valStack.Pop(); + + var tsOp = opStack.Pop(); + valStack.Push((char)tsOp switch + { + '&' => (NpgsqlTsQuery)new NpgsqlTsQueryAnd(left, right), + '|' => new NpgsqlTsQueryOr(left, right), + '<' => new NpgsqlTsQueryFollowedBy(left, tsOp.FollowedByDistance, right), + _ => throw new FormatException("Syntax error in tsquery") + }); + } + if (opStack.Count == 0) + throw new FormatException("Syntax error in tsquery: closing parenthesis without an opening parenthesis"); + opStack.Pop(); + goto PushedVal; + } + + if (ch == ':') + throw new FormatException("Unexpected : while parsing tsquery"); + + if (char.IsWhiteSpace(ch)) + goto NextToken; + + pos--; + if (expectingBinOp) + throw new FormatException("Unexpected lexeme while parsing tsquery"); + // Proceed to WaitEnd + + WaitEnd: + if (pos >= value.Length || char.IsWhiteSpace(ch = value[pos]) || ch == '!' || ch == '&' || ch == '|' || ch == '(' || ch == ')') + { + valStack.Push(new NpgsqlTsQueryLexeme(sb.ToString())); + goto PushedVal; + } + pos++; + if (ch == ':') + { + valStack.Push(new NpgsqlTsQueryLexeme(sb.ToString())); + sb.Clear(); + goto InWeightInfo; + } + if (ch == '\\') + { + if (pos >= value.Length) + throw new FormatException(@"Unexpected \ in end of value"); + ch = value[pos++]; + } + sb.Append(ch); + goto WaitEnd; + + WaitEndComplex: + if (pos >= value.Length) + throw new FormatException("Missing terminating ' in string literal"); + ch = value[pos++]; + if (ch == '\'') + { + if (pos < value.Length && value[pos] == '\'') + { + ch = '\''; + pos++; + } + else + { + valStack.Push(new NpgsqlTsQueryLexeme(sb.ToString())); + if (pos < value.Length && value[pos] == ':') + { + pos++; + goto InWeightInfo; + } + goto PushedVal; + } + } + if (ch == '\\') + { + if (pos >= value.Length) + throw new FormatException(@"Unexpected \ in end of value"); + ch = value[pos++]; + } + sb.Append(ch); + goto WaitEndComplex; + + + InWeightInfo: + if (pos >= value.Length) + goto Finish; + ch = value[pos]; + if (ch == '*') + ((NpgsqlTsQueryLexeme)valStack.Peek()).IsPrefixSearch = true; + else if (ch == 'a' || ch == 'A') + ((NpgsqlTsQueryLexeme)valStack.Peek()).Weights |= NpgsqlTsQueryLexeme.Weight.A; + else if (ch == 'b' || ch == 'B') + ((NpgsqlTsQueryLexeme)valStack.Peek()).Weights |= NpgsqlTsQueryLexeme.Weight.B; + else if (ch == 'c' || ch == 'C') + ((NpgsqlTsQueryLexeme)valStack.Peek()).Weights |= NpgsqlTsQueryLexeme.Weight.C; + else if (ch == 'd' || ch == 'D') + ((NpgsqlTsQueryLexeme)valStack.Peek()).Weights |= NpgsqlTsQueryLexeme.Weight.D; + else + goto PushedVal; + pos++; + goto InWeightInfo; + + PushedVal: + sb.Clear(); + var processTightBindingOperator = true; + while (opStack.Count > 0 && processTightBindingOperator) + { + var tsOp = opStack.Peek(); + switch (tsOp) + { + case '&': + if (valStack.Count < 2) + throw new FormatException("Syntax error in tsquery"); + var andRight = valStack.Pop(); + var andLeft = valStack.Pop(); + valStack.Push(new NpgsqlTsQueryAnd(andLeft, andRight)); + opStack.Pop(); + break; + + case '!': + if (valStack.Count == 0) + throw new FormatException("Syntax error in tsquery"); + valStack.Push(new NpgsqlTsQueryNot(valStack.Pop())); + opStack.Pop(); + break; + + case '<': + if (valStack.Count < 2) + throw new FormatException("Syntax error in tsquery"); + var followedByRight = valStack.Pop(); + var followedByLeft = valStack.Pop(); + valStack.Push( + new NpgsqlTsQueryFollowedBy( + followedByLeft, + tsOp.FollowedByDistance, + followedByRight)); + opStack.Pop(); + break; + + default: + processTightBindingOperator = false; + break; + } + } + expectingBinOp = true; + goto NextToken; + + Finish: + while (opStack.Count > 0) + { + if (valStack.Count < 2) + throw new FormatException("Syntax error in tsquery"); + + var right = valStack.Pop(); + var left = valStack.Pop(); + + var tsOp = opStack.Pop(); + var query = (char)tsOp switch + { + '&' => (NpgsqlTsQuery)new NpgsqlTsQueryAnd(left, right), + '|' => new NpgsqlTsQueryOr(left, right), + '<' => new NpgsqlTsQueryFollowedBy(left, tsOp.FollowedByDistance, right), + _ => throw new FormatException("Syntax error in tsquery") + }; + valStack.Push(query); + } + if (valStack.Count != 1) + throw new FormatException("Syntax error in tsquery"); + return valStack.Pop(); + } + + /// + public override int GetHashCode() => + throw new NotImplementedException(); + + /// + public override bool Equals(object? obj) => + obj is NpgsqlTsQuery query && query.Equals(this); + + /// + /// Returns a value indicating whether this instance and a specified object represent the same value. + /// + /// An object to compare to this instance. + /// if g is equal to this instance; otherwise, . + public abstract bool Equals(NpgsqlTsQuery? other); + + /// + /// Indicates whether the values of two specified objects are equal. + /// + /// The first object to compare. + /// The second object to compare. + /// if and are equal; otherwise, . + public static bool operator ==(NpgsqlTsQuery? left, NpgsqlTsQuery? right) => + left is null ? right is null : left.Equals(right); + + + /// + /// Indicates whether the values of two specified objects are not equal. + /// + /// The first object to compare. + /// The second object to compare. + /// if and are not equal; otherwise, . + public static bool operator !=(NpgsqlTsQuery? left, NpgsqlTsQuery? right) => + left is null ? right is not null : !left.Equals(right); +} + +readonly struct NpgsqlTsQueryOperator +{ + public readonly char Char; + public readonly int FollowedByDistance; + + public NpgsqlTsQueryOperator(char character, int followedByDistance) + { + Char = character; + FollowedByDistance = followedByDistance; + } + + public static implicit operator NpgsqlTsQueryOperator(char c) => new(c, 0); + public static implicit operator char(NpgsqlTsQueryOperator o) => o.Char; +} + +/// +/// TsQuery Lexeme node. +/// +public sealed class NpgsqlTsQueryLexeme : NpgsqlTsQuery +{ + string _text; + + /// + /// Lexeme text. + /// + public string Text + { + get => _text; + set + { + if (string.IsNullOrEmpty(value)) + throw new ArgumentException("Text is null or empty string", nameof(value)); + + _text = value; + } + } + + Weight _weights; + + /// + /// Weights is a bitmask of the Weight enum. + /// + public Weight Weights + { + get => _weights; + set + { + if (((byte)value >> 4) != 0) + throw new ArgumentOutOfRangeException(nameof(value), "Illegal weights"); + + _weights = value; + } + } + + /// + /// Prefix search. + /// + public bool IsPrefixSearch { get; set; } + + /// + /// Creates a tsquery lexeme with only lexeme text. + /// + /// Lexeme text. + public NpgsqlTsQueryLexeme(string text) : this(text, Weight.None, false) { } + + /// + /// Creates a tsquery lexeme with lexeme text and weights. + /// + /// Lexeme text. + /// Bitmask of enum Weight. + public NpgsqlTsQueryLexeme(string text, Weight weights) : this(text, weights, false) { } + + /// + /// Creates a tsquery lexeme with lexeme text, weights and prefix search flag. + /// + /// Lexeme text. + /// Bitmask of enum Weight. + /// Is prefix search? + public NpgsqlTsQueryLexeme(string text, Weight weights, bool isPrefixSearch) + : base(NodeKind.Lexeme) + { + _text = text; + Weights = weights; + IsPrefixSearch = isPrefixSearch; + } + + /// + /// Weight enum, can be OR'ed together. + /// +#pragma warning disable CA1714 + [Flags] + public enum Weight +#pragma warning restore CA1714 + { + /// + /// None + /// + None = 0, + /// + /// D + /// + D = 1, + /// + /// C + /// + C = 2, + /// + /// B + /// + B = 4, + /// + /// A + /// + A = 8 + } + + internal override void WriteCore(StringBuilder sb, bool first = false) + { + sb.Append('\'').Append(Text.Replace(@"\", @"\\").Replace("'", "''")).Append('\''); + if (IsPrefixSearch || Weights != Weight.None) + sb.Append(':'); + if (IsPrefixSearch) + sb.Append('*'); + if ((Weights & Weight.A) != Weight.None) + sb.Append('A'); + if ((Weights & Weight.B) != Weight.None) + sb.Append('B'); + if ((Weights & Weight.C) != Weight.None) + sb.Append('C'); + if ((Weights & Weight.D) != Weight.None) + sb.Append('D'); + } + + /// + public override bool Equals(NpgsqlTsQuery? other) => + other is NpgsqlTsQueryLexeme lexeme && + lexeme.Text == Text && + lexeme.Weights == Weights && + lexeme.IsPrefixSearch == IsPrefixSearch; + + /// + public override int GetHashCode() => + HashCode.Combine(Text, Weights, IsPrefixSearch); +} + +/// +/// TsQuery Not node. +/// +public sealed class NpgsqlTsQueryNot : NpgsqlTsQuery +{ + /// + /// Child node + /// + public NpgsqlTsQuery Child { get; set; } + + /// + /// Creates a not operator, with a given child node. + /// + /// + public NpgsqlTsQueryNot(NpgsqlTsQuery child) + : base(NodeKind.Not) + { + Child = child; + } + + internal override void WriteCore(StringBuilder sb, bool first = false) + { + sb.Append('!'); + if (Child == null) + { + sb.Append("''"); + } + else + { + if (Child.Kind != NodeKind.Lexeme) + sb.Append("( "); + Child.WriteCore(sb, true); + if (Child.Kind != NodeKind.Lexeme) + sb.Append(" )"); + } + } + + /// + public override bool Equals(NpgsqlTsQuery? other) => + other is NpgsqlTsQueryNot not && + not.Child == Child; + + /// + public override int GetHashCode() => + Child?.GetHashCode() ?? 0; +} + +/// +/// Base class for TsQuery binary operators (& and |). +/// +public abstract class NpgsqlTsQueryBinOp : NpgsqlTsQuery +{ + /// + /// Left child + /// + public NpgsqlTsQuery Left { get; set; } + + /// + /// Right child + /// + public NpgsqlTsQuery Right { get; set; } + + /// + /// Constructs a . + /// + protected NpgsqlTsQueryBinOp(NodeKind kind, NpgsqlTsQuery left, NpgsqlTsQuery right) + : base(kind) + { + Left = left; + Right = right; + } +} + +/// +/// TsQuery And node. +/// +public sealed class NpgsqlTsQueryAnd : NpgsqlTsQueryBinOp +{ + /// + /// Creates an and operator, with two given child nodes. + /// + /// + /// + public NpgsqlTsQueryAnd(NpgsqlTsQuery left, NpgsqlTsQuery right) + : base(NodeKind.And, left, right) {} + + internal override void WriteCore(StringBuilder sb, bool first = false) + { + Left.WriteCore(sb); + sb.Append(" & "); + Right.WriteCore(sb); + } + + /// + public override bool Equals(NpgsqlTsQuery? other) => + other is NpgsqlTsQueryAnd and && + and.Left == Left && + and.Right == Right; + + /// + public override int GetHashCode() => + HashCode.Combine(Left, Right); +} + +/// +/// TsQuery Or Node. +/// +public sealed class NpgsqlTsQueryOr : NpgsqlTsQueryBinOp +{ + /// + /// Creates an or operator, with two given child nodes. + /// + /// + /// + public NpgsqlTsQueryOr(NpgsqlTsQuery left, NpgsqlTsQuery right) + : base(NodeKind.Or, left, right) {} + + internal override void WriteCore(StringBuilder sb, bool first = false) + { + // TODO: Figure out the nullability strategy here + if (!first) + sb.Append("( "); + + Left.WriteCore(sb); + sb.Append(" | "); + Right.WriteCore(sb); + + if (!first) + sb.Append(" )"); + } + + /// + public override bool Equals(NpgsqlTsQuery? other) => + other is NpgsqlTsQueryOr or && + or.Left == Left && + or.Right == Right; + + /// + public override int GetHashCode() => + HashCode.Combine(Left, Right); +} + +/// +/// TsQuery "Followed by" Node. +/// +public sealed class NpgsqlTsQueryFollowedBy : NpgsqlTsQueryBinOp +{ + /// + /// The distance between the 2 nodes, in lexemes. + /// + public int Distance { get; set; } + + /// + /// Creates a "followed by" operator, specifying 2 child nodes and the + /// distance between them in lexemes. + /// + /// + /// + /// + public NpgsqlTsQueryFollowedBy( + NpgsqlTsQuery left, + int distance, + NpgsqlTsQuery right) + : base(NodeKind.Phrase, left, right) + { + if (distance < 0) + throw new ArgumentOutOfRangeException(nameof(distance)); + + Distance = distance; + } + + internal override void WriteCore(StringBuilder sb, bool first = false) + { + // TODO: Figure out the nullability strategy here + if (!first) + sb.Append("( "); + + Left.WriteCore(sb); + + sb.Append(" <"); + if (Distance == 1) sb.Append("-"); + else sb.Append(Distance); + sb.Append("> "); + + Right.WriteCore(sb); + + if (!first) + sb.Append(" )"); + } + + /// + public override bool Equals(NpgsqlTsQuery? other) => + other is NpgsqlTsQueryFollowedBy followedBy && + followedBy.Left == Left && + followedBy.Right == Right && + followedBy.Distance == Distance; + + /// + public override int GetHashCode() => + HashCode.Combine(Left, Right, Distance); +} + +/// +/// Represents an empty tsquery. Shold only be used as top node. +/// +public sealed class NpgsqlTsQueryEmpty : NpgsqlTsQuery +{ + /// + /// Creates a tsquery that represents an empty query. Should not be used as child node. + /// + public NpgsqlTsQueryEmpty() : base(NodeKind.Empty) {} + + internal override void WriteCore(StringBuilder sb, bool first = false) { } + + /// + public override bool Equals(NpgsqlTsQuery? other) => + other is NpgsqlTsQueryEmpty; + + /// + public override int GetHashCode() => + Kind.GetHashCode(); +} diff --git a/LibExternal/Npgsql/NpgsqlTypes/NpgsqlTsVector.cs b/LibExternal/Npgsql/NpgsqlTypes/NpgsqlTsVector.cs new file mode 100644 index 0000000..cf98f53 --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlTypes/NpgsqlTsVector.cs @@ -0,0 +1,554 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +#pragma warning disable CA1040, CA1034 +// ReSharper disable once CheckNamespace +namespace NpgsqlTypes; + +/// +/// Represents a PostgreSQL tsvector. +/// +public sealed class NpgsqlTsVector : IEnumerable, IEquatable +{ + readonly List _lexemes; + + internal NpgsqlTsVector(List lexemes, bool noCheck = false) + { + if (noCheck) + { + _lexemes = lexemes; + return; + } + + _lexemes = new List(lexemes); + + if (_lexemes.Count == 0) + return; + + // Culture-specific comparisons doesn't really matter for the backend. It's sorting on its own if it detects an unsorted collection. + // Only when a .NET user wants to print the sort order. + _lexemes.Sort((a, b) => string.Compare(a.Text, b.Text, StringComparison.CurrentCulture)); + + var res = 0; + var pos = 1; + while (pos < _lexemes.Count) + { + if (_lexemes[pos].Text != _lexemes[res].Text) + { + // We're done with this lexeme. First make sure the word pos list is sorted and contains unique elements. + _lexemes[res] = new Lexeme(_lexemes[res].Text, Lexeme.UniquePos(_lexemes[res].WordEntryPositions), true); + res++; + if (res != pos) + _lexemes[res] = _lexemes[pos]; + } + else + { + // Just concatenate the word pos lists + var wordEntryPositions = _lexemes[res].WordEntryPositions; + if (wordEntryPositions != null) + { + var lexeme = _lexemes[pos]; + if (lexeme.WordEntryPositions != null) + wordEntryPositions.AddRange(lexeme.WordEntryPositions); + } + else + { + _lexemes[res] = _lexemes[pos]; + } + } + pos++; + } + + // Last element + _lexemes[res] = new Lexeme(_lexemes[res].Text, Lexeme.UniquePos(_lexemes[res].WordEntryPositions), true); + if (res != pos - 1) + { + _lexemes.RemoveRange(res, pos - 1 - res); + } + } + + /// + /// Parses a tsvector in PostgreSQL's text format. + /// + /// + /// + public static NpgsqlTsVector Parse(string value) + { + if (value == null) + throw new ArgumentNullException(nameof(value)); + + var lexemes = new List(); + var pos = 0; + var wordPos = 0; + var sb = new StringBuilder(); + List wordEntryPositions; + + WaitWord: + if (pos >= value.Length) + goto Finish; + if (char.IsWhiteSpace(value[pos])) + { + pos++; + goto WaitWord; + } + sb.Clear(); + if (value[pos] == '\'') + { + pos++; + goto WaitEndComplex; + } + if (value[pos] == '\\') + { + pos++; + goto WaitNextChar; + } + sb.Append(value[pos++]); + goto WaitEndWord; + + WaitNextChar: + if (pos >= value.Length) + throw new FormatException("Missing escaped character after \\ at end of value"); + sb.Append(value[pos++]); + + WaitEndWord: + if (pos >= value.Length || char.IsWhiteSpace(value[pos])) + { + lexemes.Add(new Lexeme(sb.ToString())); + if (pos >= value.Length) + goto Finish; + pos++; + goto WaitWord; + } + if (value[pos] == '\\') + { + pos++; + goto WaitNextChar; + } + if (value[pos] == ':') + { + pos++; + goto StartPosInfo; + } + sb.Append(value[pos++]); + goto WaitEndWord; + + WaitEndComplex: + if (pos >= value.Length) + throw new FormatException("Unexpected end of value"); + if (value[pos] == '\'') + { + pos++; + goto WaitCharComplex; + } + if (value[pos] == '\\') + { + pos++; + if (pos >= value.Length) + throw new FormatException("Missing escaped character after \\ at end of value"); + } + sb.Append(value[pos++]); + goto WaitEndComplex; + + WaitCharComplex: + if (pos < value.Length && value[pos] == '\'') + { + sb.Append('\''); + pos++; + goto WaitEndComplex; + } + if (pos < value.Length && value[pos] == ':') + { + pos++; + goto StartPosInfo; + } + lexemes.Add(new Lexeme(sb.ToString())); + goto WaitWord; + + StartPosInfo: + wordEntryPositions = new List(); + + InPosInfo: + var digitPos = pos; + while (pos < value.Length && value[pos] >= '0' && value[pos] <= '9') + pos++; + if (digitPos == pos) + throw new FormatException("Missing length after :"); + wordPos = int.Parse(value.Substring(digitPos, pos - digitPos)); + + // Note: PostgreSQL backend parser matches also for example 1DD2A, which is parsed into 1A, but not 1AA2D ... + if (pos < value.Length) + { + if (value[pos] == 'A' || value[pos] == 'a' || value[pos] == '*') // Why * ? + { + wordEntryPositions.Add(new Lexeme.WordEntryPos(wordPos, Lexeme.Weight.A)); + pos++; + goto WaitPosDelim; + } + if (value[pos] >= 'B' && value[pos] <= 'D' || value[pos] >= 'b' && value[pos] <= 'd') + { + var weight = value[pos]; + if (weight >= 'b' && weight <= 'd') + weight = (char)(weight - ('b' - 'B')); + wordEntryPositions.Add(new Lexeme.WordEntryPos(wordPos, Lexeme.Weight.D + ('D' - weight))); + pos++; + goto WaitPosDelim; + } + } + wordEntryPositions.Add(new Lexeme.WordEntryPos(wordPos)); + + WaitPosDelim: + if (pos >= value.Length || char.IsWhiteSpace(value[pos])) + { + if (pos < value.Length) + pos++; + lexemes.Add(new Lexeme(sb.ToString(), wordEntryPositions)); + goto WaitWord; + } + if (value[pos] == ',') + { + pos++; + goto InPosInfo; + } + throw new FormatException("Missing comma, whitespace or end of value after lexeme pos info"); + + Finish: + return new NpgsqlTsVector(lexemes); + } + + /// + /// Returns the lexeme at a specific index + /// + /// + /// + public Lexeme this[int index] + { + get + { + if (index < 0 || index >= _lexemes.Count) + throw new ArgumentException(nameof(index)); + + return _lexemes[index]; + } + } + + /// + /// Gets the number of lexemes. + /// + public int Count => _lexemes.Count; + + /// + /// Returns an enumerator. + /// + /// + public IEnumerator GetEnumerator() => _lexemes.GetEnumerator(); + + /// + /// Returns an enumerator. + /// + /// + IEnumerator IEnumerable.GetEnumerator() => _lexemes.GetEnumerator(); + + /// + /// Gets a string representation in PostgreSQL's format. + /// + /// + public override string ToString() => string.Join(" ", _lexemes); + + /// + public bool Equals(NpgsqlTsVector? other) + { + if (ReferenceEquals(this, other)) + return true; + + if (other is null || _lexemes.Count != other._lexemes.Count) + return false; + + for (var i = 0; i < _lexemes.Count; i++) + if (!_lexemes[i].Equals(other._lexemes[i])) + return false; + + return true; + } + + /// + public override bool Equals(object? obj) + => obj is NpgsqlTsVector other && Equals(other); + + /// + public override int GetHashCode() + { + var hash = new HashCode(); + + foreach (var lexeme in _lexemes) + hash.Add(lexeme); + + return hash.ToHashCode(); + } + + /// + /// Represents a lexeme. A lexeme consists of a text string and optional word entry positions. + /// + public struct Lexeme : IEquatable + { + /// + /// Gets or sets the text. + /// + public string Text { get; set; } + + internal readonly List? WordEntryPositions; + + /// + /// Creates a lexeme with no word entry positions. + /// + /// + public Lexeme(string text) + { + Text = text; + WordEntryPositions = null; + } + + /// + /// Creates a lexeme with word entry positions. + /// + /// + /// + public Lexeme(string text, List? wordEntryPositions) + : this(text, wordEntryPositions, false) {} + + internal Lexeme(string text, List? wordEntryPositions, bool noCopy) + { + Text = text; + if (wordEntryPositions != null) + WordEntryPositions = noCopy ? wordEntryPositions : new List(wordEntryPositions); + else + WordEntryPositions = null; + } + + internal static List? UniquePos(List? list) + { + if (list == null) + return null; + var needsProcessing = false; + for (var i = 1; i < list.Count; i++) + { + if (list[i - 1].Pos >= list[i].Pos) + { + needsProcessing = true; + break; + } + } + if (!needsProcessing) + return list; + + // Don't change the original list, as the user might inspect it later if he holds a reference to the lexeme's list + list = new List(list); + + list.Sort((x, y) => x.Pos.CompareTo(y.Pos)); + + var a = 0; + for (var b = 1; b < list.Count; b++) + { + if (list[a].Pos != list[b].Pos) + { + a++; + if (a != b) + list[a] = list[b]; + } + else if (list[b].Weight > list[a].Weight) + list[a] = list[b]; + } + if (a != list.Count - 1) + { + list.RemoveRange(a, list.Count - 1 - a); + } + return list; + } + + /// + /// Gets a word entry position. + /// + /// + /// + public WordEntryPos this[int index] + { + get + { + if (index < 0 || WordEntryPositions == null || index >= WordEntryPositions.Count) + throw new ArgumentException(nameof(index)); + + return WordEntryPositions[index]; + } + internal set + { + if (index < 0 || WordEntryPositions == null || index >= WordEntryPositions.Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + WordEntryPositions[index] = value; + } + } + + /// + /// Gets the number of word entry positions. + /// + public int Count => WordEntryPositions?.Count ?? 0; + + /// + /// Creates a string representation in PostgreSQL's format. + /// + /// + public override string ToString() + { + var str = '\'' + (Text ?? "").Replace(@"\", @"\\").Replace("'", "''") + '\''; + if (Count > 0) + str += ":" + string.Join(",", WordEntryPositions!); + return str; + } + + /// + /// Represents a word entry position and an optional weight. + /// + public struct WordEntryPos : IEquatable + { + internal short Value { get; } + + internal WordEntryPos(short value) + { + Value = value; + } + + /// + /// Creates a WordEntryPos with a given position and weight. + /// + /// Position values can range from 1 to 16383; larger numbers are silently set to 16383. + /// A weight labeled between A and D. + public WordEntryPos(int pos, Weight weight = Weight.D) + { + if (pos == 0) + throw new ArgumentOutOfRangeException(nameof(pos), "Lexeme position is out of range. Min value is 1, max value is 2^14-1. Value was: " + pos); + if (weight < Weight.D || weight > Weight.A) + throw new ArgumentOutOfRangeException(nameof(weight)); + + // Per documentation: "Position values can range from 1 to 16383; larger numbers are silently set to 16383." + if (pos >> 14 != 0) + pos = (1 << 14) - 1; + + Value = (short)(((int)weight << 14) | pos); + } + + /// + /// The weight is labeled from A to D. D is the default, and not printed. + /// + public Weight Weight => (Weight)((Value >> 14) & 3); + + /// + /// The position is a 14-bit unsigned integer indicating the position in the text this lexeme occurs. Cannot be 0. + /// + public int Pos => Value & ((1 << 14) - 1); + + /// + /// Prints this lexeme in PostgreSQL's format, i.e. position is followed by weight (weight is only printed if A, B or C). + /// + /// + public override string ToString() + { + if (Weight != Weight.D) + return Pos + Weight.ToString(); + return Pos.ToString(); + } + + /// + /// Determines whether the specified object is equal to the current object. + /// + public bool Equals(WordEntryPos o) => Value == o.Value; + + /// + /// Determines whether the specified object is equal to the current object. + /// + public override bool Equals(object? o) => o is WordEntryPos pos && Equals(pos); + + /// + /// Gets a hash code for the current object. + /// + public override int GetHashCode() => Value.GetHashCode(); + + /// + /// Determines whether the specified object is equal to the current object. + /// + public static bool operator ==(WordEntryPos left, WordEntryPos right) => left.Equals(right); + + /// + /// Determines whether the specified object is unequal to the current object. + /// + public static bool operator !=(WordEntryPos left, WordEntryPos right) => !left.Equals(right); + } + + /// + /// The weight is labeled from A to D. D is the default, and not printed. + /// + public enum Weight + { + /// + /// D, the default + /// + D = 0, + + /// + /// C + /// + C = 1, + + /// + /// B + /// + B = 2, + + /// + /// A + /// + A = 3 + } + + /// + /// Determines whether the specified object is equal to the current object. + /// + public bool Equals(Lexeme o) + { + if (Text != o.Text) + return false; + + if (WordEntryPositions is null) + return o.WordEntryPositions is null; + + if (o.WordEntryPositions is null || WordEntryPositions.Count != o.WordEntryPositions.Count) + return false; + + for (var i = 0; i < WordEntryPositions.Count; i++) + if (!WordEntryPositions[i].Equals(o.WordEntryPositions[i])) + return false; + + return true; + } + + /// + /// Determines whether the specified object is equal to the current object. + /// + public override bool Equals(object? o) => o is Lexeme lexeme && Equals(lexeme); + + /// + /// Gets a hash code for the current object. + /// + public override int GetHashCode() => Text.GetHashCode(); + + /// + /// Determines whether the specified object is equal to the current object. + /// + public static bool operator ==(Lexeme left, Lexeme right) => left.Equals(right); + + /// + /// Determines whether the specified object is unequal to the current object. + /// + public static bool operator !=(Lexeme left, Lexeme right) => !left.Equals(right); + } +} diff --git a/LibExternal/Npgsql/NpgsqlTypes/NpgsqlTypes.cs b/LibExternal/Npgsql/NpgsqlTypes/NpgsqlTypes.cs new file mode 100644 index 0000000..d34ccb4 --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlTypes/NpgsqlTypes.cs @@ -0,0 +1,659 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Text.RegularExpressions; +using Npgsql.Util; + +#pragma warning disable 1591 + +// ReSharper disable once CheckNamespace +namespace NpgsqlTypes; + +/// +/// Represents a PostgreSQL point type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-geometric.html +/// +public struct NpgsqlPoint : IEquatable +{ + static readonly Regex Regex = new(@"\((-?\d+.?\d*),(-?\d+.?\d*)\)"); + + public double X { get; set; } + public double Y { get; set; } + + public NpgsqlPoint(double x, double y) + : this() + { + X = x; + Y = y; + } + + // ReSharper disable CompareOfFloatsByEqualityOperator + public bool Equals(NpgsqlPoint other) => X == other.X && Y == other.Y; + // ReSharper restore CompareOfFloatsByEqualityOperator + + public override bool Equals(object? obj) + => obj is NpgsqlPoint point && Equals(point); + + public static bool operator ==(NpgsqlPoint x, NpgsqlPoint y) => x.Equals(y); + + public static bool operator !=(NpgsqlPoint x, NpgsqlPoint y) => !(x == y); + + public override int GetHashCode() + => X.GetHashCode() ^ PGUtil.RotateShift(Y.GetHashCode(), PGUtil.BitsInInt / 2); + + public static NpgsqlPoint Parse(string s) + { + var m = Regex.Match(s); + if (!m.Success) { + throw new FormatException("Not a valid point: " + s); + } + return new NpgsqlPoint(double.Parse(m.Groups[1].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat), + double.Parse(m.Groups[2].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat)); + } + + public override string ToString() + => string.Format(CultureInfo.InvariantCulture, "({0},{1})", X, Y); +} + +/// +/// Represents a PostgreSQL line type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-geometric.html +/// +public struct NpgsqlLine : IEquatable +{ + static readonly Regex Regex = new(@"\{(-?\d+.?\d*),(-?\d+.?\d*),(-?\d+.?\d*)\}"); + + public double A { get; set; } + public double B { get; set; } + public double C { get; set; } + + public NpgsqlLine(double a, double b, double c) + : this() + { + A = a; + B = b; + C = c; + } + + public static NpgsqlLine Parse(string s) + { + var m = Regex.Match(s); + if (!m.Success) + throw new FormatException("Not a valid line: " + s); + return new NpgsqlLine( + double.Parse(m.Groups[1].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat), + double.Parse(m.Groups[2].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat), + double.Parse(m.Groups[3].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat) + ); + } + + public override string ToString() + => string.Format(CultureInfo.InvariantCulture, "{{{0},{1},{2}}}", A, B, C); + + public override int GetHashCode() => A.GetHashCode() * B.GetHashCode() * C.GetHashCode(); + + public bool Equals(NpgsqlLine other) => A == other.A && B == other.B && C == other.C; + + public override bool Equals(object? obj) + => obj is NpgsqlLine line && Equals(line); + + public static bool operator ==(NpgsqlLine x, NpgsqlLine y) => x.Equals(y); + public static bool operator !=(NpgsqlLine x, NpgsqlLine y) => !(x == y); +} + +/// +/// Represents a PostgreSQL Line Segment type. +/// +public struct NpgsqlLSeg : IEquatable +{ + static readonly Regex Regex = new(@"\[\((-?\d+.?\d*),(-?\d+.?\d*)\),\((-?\d+.?\d*),(-?\d+.?\d*)\)\]"); + + public NpgsqlPoint Start { get; set; } + public NpgsqlPoint End { get; set; } + + public NpgsqlLSeg(NpgsqlPoint start, NpgsqlPoint end) + : this() + { + Start = start; + End = end; + } + + public NpgsqlLSeg(double startx, double starty, double endx, double endy) : this() + { + Start = new NpgsqlPoint(startx, starty); + End = new NpgsqlPoint(endx, endy); + } + + public static NpgsqlLSeg Parse(string s) + { + var m = Regex.Match(s); + if (!m.Success) { + throw new FormatException("Not a valid line: " + s); + } + return new NpgsqlLSeg( + double.Parse(m.Groups[1].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat), + double.Parse(m.Groups[2].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat), + double.Parse(m.Groups[3].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat), + double.Parse(m.Groups[4].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat) + ); + + } + + public override string ToString() + => string.Format(CultureInfo.InvariantCulture, "[{0},{1}]", Start, End); + + public override int GetHashCode() + => Start.X.GetHashCode() ^ + PGUtil.RotateShift(Start.Y.GetHashCode(), PGUtil.BitsInInt / 4) ^ + PGUtil.RotateShift(End.X.GetHashCode(), PGUtil.BitsInInt / 2) ^ + PGUtil.RotateShift(End.Y.GetHashCode(), PGUtil.BitsInInt * 3 / 4); + + public bool Equals(NpgsqlLSeg other) => Start == other.Start && End == other.End; + + public override bool Equals(object? obj) + => obj is NpgsqlLSeg seg && Equals(seg); + + public static bool operator ==(NpgsqlLSeg x, NpgsqlLSeg y) => x.Equals(y); + public static bool operator !=(NpgsqlLSeg x, NpgsqlLSeg y) => !(x == y); +} + +/// +/// Represents a PostgreSQL box type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-geometric.html +/// +public struct NpgsqlBox : IEquatable +{ + static readonly Regex Regex = new(@"\((-?\d+.?\d*),(-?\d+.?\d*)\),\((-?\d+.?\d*),(-?\d+.?\d*)\)"); + + public NpgsqlPoint UpperRight { get; set; } + public NpgsqlPoint LowerLeft { get; set; } + + public NpgsqlBox(NpgsqlPoint upperRight, NpgsqlPoint lowerLeft) : this() + { + UpperRight = upperRight; + LowerLeft = lowerLeft; + } + + public NpgsqlBox(double top, double right, double bottom, double left) + : this(new NpgsqlPoint(right, top), new NpgsqlPoint(left, bottom)) { } + + public double Left => LowerLeft.X; + public double Right => UpperRight.X; + public double Bottom => LowerLeft.Y; + public double Top => UpperRight.Y; + public double Width => Right - Left; + public double Height => Top - Bottom; + + public bool IsEmpty => Width == 0 || Height == 0; + + public bool Equals(NpgsqlBox other) => UpperRight == other.UpperRight && LowerLeft == other.LowerLeft; + + public override bool Equals(object? obj) + => obj is NpgsqlBox box && Equals(box); + + public static bool operator ==(NpgsqlBox x, NpgsqlBox y) => x.Equals(y); + public static bool operator !=(NpgsqlBox x, NpgsqlBox y) => !(x == y); + public override string ToString() + => string.Format(CultureInfo.InvariantCulture, "{0},{1}", UpperRight, LowerLeft); + + public static NpgsqlBox Parse(string s) + { + var m = Regex.Match(s); + return new NpgsqlBox( + new NpgsqlPoint(double.Parse(m.Groups[1].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat), + double.Parse(m.Groups[2].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat)), + new NpgsqlPoint(double.Parse(m.Groups[3].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat), + double.Parse(m.Groups[4].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat)) + ); + } + + public override int GetHashCode() + => Top.GetHashCode() ^ + PGUtil.RotateShift(Right.GetHashCode(), PGUtil.BitsInInt / 4) ^ + PGUtil.RotateShift(Bottom.GetHashCode(), PGUtil.BitsInInt / 2) ^ + PGUtil.RotateShift(LowerLeft.GetHashCode(), PGUtil.BitsInInt * 3 / 4); +} + +/// +/// Represents a PostgreSQL Path type. +/// +public struct NpgsqlPath : IList, IEquatable +{ + readonly List _points; + public bool Open { get; set; } + + public NpgsqlPath() + { + _points = new(); + Open = false; + } + + public NpgsqlPath(IEnumerable points, bool open) + { + _points = new List(points); + Open = open; + } + + public NpgsqlPath(IEnumerable points) : this(points, false) {} + public NpgsqlPath(params NpgsqlPoint[] points) : this(points, false) {} + + public NpgsqlPath(bool open) : this() + { + _points = new List(); + Open = open; + } + + public NpgsqlPath(int capacity, bool open) : this() + { + _points = new List(capacity); + Open = open; + } + + public NpgsqlPath(int capacity) : this(capacity, false) {} + + public NpgsqlPoint this[int index] + { + get => _points[index]; + set => _points[index] = value; + } + + public int Capacity => _points.Capacity; + public int Count => _points.Count; + public bool IsReadOnly => false; + + public int IndexOf(NpgsqlPoint item) => _points.IndexOf(item); + public void Insert(int index, NpgsqlPoint item) => _points.Insert(index, item); + public void RemoveAt(int index) => _points.RemoveAt(index); + public void Add(NpgsqlPoint item) => _points.Add(item); + public void Clear() => _points.Clear(); + public bool Contains(NpgsqlPoint item) => _points.Contains(item); + public void CopyTo(NpgsqlPoint[] array, int arrayIndex) => _points.CopyTo(array, arrayIndex); + public bool Remove(NpgsqlPoint item) => _points.Remove(item); + public IEnumerator GetEnumerator() => _points.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public bool Equals(NpgsqlPath other) + { + if (Open != other.Open || Count != other.Count) + return false; + if (ReferenceEquals(_points, other._points))//Short cut for shallow copies. + return true; + for (var i = 0; i != Count; ++i) + if (this[i] != other[i]) + return false; + return true; + } + + public override bool Equals(object? obj) + => obj is NpgsqlPath path && Equals(path); + + public static bool operator ==(NpgsqlPath x, NpgsqlPath y) => x.Equals(y); + public static bool operator !=(NpgsqlPath x, NpgsqlPath y) => !(x == y); + + public override int GetHashCode() + { + var ret = 266370105;//seed with something other than zero to make paths of all zeros hash differently. + foreach (var point in this) + { + //The ideal amount to shift each value is one that would evenly spread it throughout + //the resultant bytes. Using the current result % 32 is essentially using a random value + //but one that will be the same on subsequent calls. + ret ^= PGUtil.RotateShift(point.GetHashCode(), ret % PGUtil.BitsInInt); + } + return Open ? ret : -ret; + } + + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append(Open ? '[' : '('); + int i; + for (i = 0; i < _points.Count; i++) + { + var p = _points[i]; + sb.AppendFormat(CultureInfo.InvariantCulture, "({0},{1})", p.X, p.Y); + if (i < _points.Count - 1) + sb.Append(","); + } + sb.Append(Open ? ']' : ')'); + return sb.ToString(); + } + + public static NpgsqlPath Parse(string s) + { + var open = s[0] switch + { + '[' => true, + '(' => false, + _ => throw new Exception("Invalid path string: " + s) + }; + Debug.Assert(s[s.Length - 1] == (open ? ']' : ')')); + var result = new NpgsqlPath(open); + var i = 1; + while (true) + { + var i2 = s.IndexOf(')', i); + result.Add(NpgsqlPoint.Parse(s.Substring(i, i2 - i + 1))); + if (s[i2 + 1] != ',') + break; + i = i2 + 2; + } + return result; + } +} + +/// +/// Represents a PostgreSQL Polygon type. +/// +public struct NpgsqlPolygon : IList, IEquatable +{ + readonly List _points; + + public NpgsqlPolygon() + => _points = new(); + + public NpgsqlPolygon(IEnumerable points) + { + _points = new List(points); + } + + public NpgsqlPolygon(params NpgsqlPoint[] points) : this((IEnumerable) points) {} + + public NpgsqlPolygon(int capacity) + { + _points = new List(capacity); + } + + public NpgsqlPoint this[int index] + { + get => _points[index]; + set => _points[index] = value; + } + + public int Capacity => _points.Capacity; + public int Count => _points.Count; + public bool IsReadOnly => false; + + public int IndexOf(NpgsqlPoint item) => _points.IndexOf(item); + public void Insert(int index, NpgsqlPoint item) => _points.Insert(index, item); + public void RemoveAt(int index) => _points.RemoveAt(index); + public void Add(NpgsqlPoint item) => _points.Add(item); + public void Clear() => _points.Clear(); + public bool Contains(NpgsqlPoint item) => _points.Contains(item); + public void CopyTo(NpgsqlPoint[] array, int arrayIndex) => _points.CopyTo(array, arrayIndex); + public bool Remove(NpgsqlPoint item) => _points.Remove(item); + public IEnumerator GetEnumerator() => _points.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public bool Equals(NpgsqlPolygon other) + { + if (Count != other.Count) + return false; + if (ReferenceEquals(_points, other._points)) + return true; + for (var i = 0; i != Count; ++i) + if (this[i] != other[i]) + return false; + return true; + } + + public override bool Equals(object? obj) + => obj is NpgsqlPolygon polygon && Equals(polygon); + + public static bool operator ==(NpgsqlPolygon x, NpgsqlPolygon y) => x.Equals(y); + public static bool operator !=(NpgsqlPolygon x, NpgsqlPolygon y) => !(x == y); + + public override int GetHashCode() + { + var ret = 266370105;//seed with something other than zero to make paths of all zeros hash differently. + foreach (var point in this) + { + //The ideal amount to shift each value is one that would evenly spread it throughout + //the resultant bytes. Using the current result % 32 is essentially using a random value + //but one that will be the same on subsequent calls. + ret ^= PGUtil.RotateShift(point.GetHashCode(), ret % PGUtil.BitsInInt); + } + return ret; + } + + public static NpgsqlPolygon Parse(string s) + { + var points = new List(); + var i = 1; + while (true) + { + var i2 = s.IndexOf(')', i); + points.Add(NpgsqlPoint.Parse(s.Substring(i, i2 - i + 1))); + if (s[i2 + 1] != ',') + break; + i = i2 + 2; + } + return new NpgsqlPolygon(points); + } + + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append('('); + int i; + for (i = 0; i < _points.Count; i++) + { + var p = _points[i]; + sb.AppendFormat(CultureInfo.InvariantCulture, "({0},{1})", p.X, p.Y); + if (i < _points.Count - 1) { + sb.Append(","); + } + } + sb.Append(')'); + return sb.ToString(); + } +} + +/// +/// Represents a PostgreSQL Circle type. +/// +public struct NpgsqlCircle : IEquatable +{ + static readonly Regex Regex = new(@"<\((-?\d+.?\d*),(-?\d+.?\d*)\),(\d+.?\d*)>"); + + public double X { get; set; } + public double Y { get; set; } + public double Radius { get; set; } + + public NpgsqlCircle(NpgsqlPoint center, double radius) + : this() + { + X = center.X; + Y = center.Y; + Radius = radius; + } + + public NpgsqlCircle(double x, double y, double radius) : this() + { + X = x; + Y = y; + Radius = radius; + } + + public NpgsqlPoint Center + { + get => new(X, Y); + set + { + X = value.X; + Y = value.Y; + } + } + + // ReSharper disable CompareOfFloatsByEqualityOperator + public bool Equals(NpgsqlCircle other) + => X == other.X && Y == other.Y && Radius == other.Radius; + // ReSharper restore CompareOfFloatsByEqualityOperator + + public override bool Equals(object? obj) + => obj is NpgsqlCircle circle && Equals(circle); + + public static NpgsqlCircle Parse(string s) + { + var m = Regex.Match(s); + if (!m.Success) + throw new FormatException("Not a valid circle: " + s); + + return new NpgsqlCircle( + double.Parse(m.Groups[1].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat), + double.Parse(m.Groups[2].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat), + double.Parse(m.Groups[3].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat) + ); + } + + public override string ToString() + => string.Format(CultureInfo.InvariantCulture, "<({0},{1}),{2}>", X, Y, Radius); + + public static bool operator ==(NpgsqlCircle x, NpgsqlCircle y) => x.Equals(y); + public static bool operator !=(NpgsqlCircle x, NpgsqlCircle y) => !(x == y); + + public override int GetHashCode() + => X.GetHashCode() * Y.GetHashCode() * Radius.GetHashCode(); +} + +/// +/// Represents a PostgreSQL inet type, which is a combination of an IPAddress and a +/// subnet mask. +/// +/// +/// https://www.postgresql.org/docs/current/static/datatype-net-types.html +/// +[Obsolete("Use ValueTuple instead")] +public struct NpgsqlInet : IEquatable +{ + public IPAddress Address { get; set; } + public int Netmask { get; set; } + + public NpgsqlInet(IPAddress address, int netmask) + { + if (address.AddressFamily != AddressFamily.InterNetwork && address.AddressFamily != AddressFamily.InterNetworkV6) + throw new ArgumentException("Only IPAddress of InterNetwork or InterNetworkV6 address families are accepted", nameof(address)); + + Address = address; + Netmask = netmask; + } + + public NpgsqlInet(IPAddress address) + { + if (address.AddressFamily != AddressFamily.InterNetwork && address.AddressFamily != AddressFamily.InterNetworkV6) + throw new ArgumentException("Only IPAddress of InterNetwork or InterNetworkV6 address families are accepted", nameof(address)); + + Address = address; + Netmask = address.AddressFamily == AddressFamily.InterNetwork ? 32 : 128; + } + + public NpgsqlInet(string addr) + { + if (addr.IndexOf('/') > 0) + { + var addrbits = addr.Split('/'); + if (addrbits.GetUpperBound(0) != 1) { + throw new FormatException("Invalid number of parts in CIDR specification"); + } + Address = IPAddress.Parse(addrbits[0]); + Netmask = int.Parse(addrbits[1]); + } + else + { + Address = IPAddress.Parse(addr); + Netmask = 32; + } + } + + public override string ToString() + { + if ((Address.AddressFamily == AddressFamily.InterNetwork && Netmask == 32) || + (Address.AddressFamily == AddressFamily.InterNetworkV6 && Netmask == 128)) + { + return Address.ToString(); + } + return $"{Address}/{Netmask}"; + } + + // ReSharper disable once InconsistentNaming + public static IPAddress ToIPAddress(NpgsqlInet inet) + { + if (inet.Netmask != 32) + throw new InvalidCastException("Cannot cast CIDR network to address"); + return inet.Address; + } + + public static explicit operator IPAddress(NpgsqlInet inet) => ToIPAddress(inet); + + public static NpgsqlInet ToNpgsqlInet(IPAddress? ip) + => ip is null ? default : new NpgsqlInet(ip); + //=> ReferenceEquals(ip, null) ? default : new NpgsqlInet(ip); + + public static implicit operator NpgsqlInet(IPAddress ip) => ToNpgsqlInet(ip); + + public void Deconstruct(out IPAddress address, out int netmask) + { + address = Address; + netmask = Netmask; + } + + public bool Equals(NpgsqlInet other) => Address.Equals(other.Address) && Netmask == other.Netmask; + + public override bool Equals(object? obj) + => obj is NpgsqlInet inet && Equals(inet); + + public override int GetHashCode() + => PGUtil.RotateShift(Address.GetHashCode(), Netmask%32); + + public static bool operator ==(NpgsqlInet x, NpgsqlInet y) => x.Equals(y); + public static bool operator !=(NpgsqlInet x, NpgsqlInet y) => !(x == y); +} + +/// +/// Represents a PostgreSQL tid value +/// +/// +/// https://www.postgresql.org/docs/current/static/datatype-oid.html +/// +public readonly struct NpgsqlTid : IEquatable +{ + /// + /// Block number + /// + public uint BlockNumber { get; } + + /// + /// Tuple index within block + /// + public ushort OffsetNumber { get; } + + public NpgsqlTid(uint blockNumber, ushort offsetNumber) + { + BlockNumber = blockNumber; + OffsetNumber = offsetNumber; + } + + public bool Equals(NpgsqlTid other) + => BlockNumber == other.BlockNumber && OffsetNumber == other.OffsetNumber; + + public override bool Equals(object? o) + => o is NpgsqlTid tid && Equals(tid); + + public override int GetHashCode() => (int)BlockNumber ^ OffsetNumber; + public static bool operator ==(NpgsqlTid left, NpgsqlTid right) => left.Equals(right); + public static bool operator !=(NpgsqlTid left, NpgsqlTid right) => !(left == right); + public override string ToString() => $"({BlockNumber},{OffsetNumber})"; +} + +#pragma warning restore 1591 diff --git a/LibExternal/Npgsql/NpgsqlTypes/PgNameAttribute.cs b/LibExternal/Npgsql/NpgsqlTypes/PgNameAttribute.cs new file mode 100644 index 0000000..35cedc7 --- /dev/null +++ b/LibExternal/Npgsql/NpgsqlTypes/PgNameAttribute.cs @@ -0,0 +1,31 @@ +using System; + +// ReSharper disable once CheckNamespace +namespace NpgsqlTypes; + +/// +/// Indicates that this property or field correspond to a PostgreSQL field with the specified name +/// +[AttributeUsage( + AttributeTargets.Enum | + AttributeTargets.Class | + AttributeTargets.Struct | + AttributeTargets.Field | + AttributeTargets.Property | + AttributeTargets.Parameter)] +public class PgNameAttribute : Attribute +{ + /// + /// The name of PostgreSQL field that corresponds to this CLR property or field + /// + public string PgName { get; private set; } + + /// + /// Indicates that this property or field correspond to a PostgreSQL field with the specified name + /// + /// The name of PostgreSQL field that corresponds to this CLR property or field + public PgNameAttribute(string pgName) + { + PgName = pgName; + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/PgPassFile.cs b/LibExternal/Npgsql/PgPassFile.cs new file mode 100644 index 0000000..b6bfb42 --- /dev/null +++ b/LibExternal/Npgsql/PgPassFile.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Npgsql; + +/// +/// Represents a .pgpass file, which contains passwords for noninteractive connections +/// +class PgPassFile +{ + #region Properties + + /// + /// File name being parsed for credentials + /// + internal string FileName { get; } + + #endregion + + #region Construction + + /// + /// Initializes a new instance of the class + /// + /// + public PgPassFile(string fileName) + => FileName = fileName; + + #endregion + + /// + /// Parses file content and gets all credentials from the file + /// + /// corresponding to all lines in the .pgpass file + internal IEnumerable Entries => File.ReadLines(FileName) + .Select(line => line.Trim()) + .Where(line => line.Any() && line[0] != '#') + .Select(Entry.Parse); + + /// + /// Searches queries loaded from .PGPASS file to find first entry matching the provided parameters. + /// + /// Hostname to query. Use null to match any. + /// Port to query. Use null to match any. + /// Database to query. Use null to match any. + /// User name to query. Use null to match any. + /// Matching if match was found. Otherwise, returns null. + internal Entry? GetFirstMatchingEntry(string? host = null, int? port = null, string? database = null, string? username = null) + => Entries.FirstOrDefault(entry => entry.IsMatch(host, port, database, username)); + + /// + /// Represents a hostname, port, database, username, and password combination that has been retrieved from a .pgpass file + /// + internal class Entry + { + const string PgPassWildcard = "*"; + + #region Fields and Properties + + /// + /// Hostname parsed from the .pgpass file + /// + internal string? Host { get; } + /// + /// Port parsed from the .pgpass file + /// + internal int? Port { get; } + /// + /// Database parsed from the .pgpass file + /// + internal string? Database { get; } + /// + /// User name parsed from the .pgpass file + /// + internal string? Username { get; } + /// + /// Password parsed from the .pgpass file + /// + internal string? Password { get; } + + #endregion + + #region Construction / Initialization + + /// + /// This class represents an entry from the .pgpass file + /// + /// Hostname parsed from the .pgpass file + /// Port parsed from the .pgpass file + /// Database parsed from the .pgpass file + /// User name parsed from the .pgpass file + /// Password parsed from the .pgpass file + Entry(string? host, int? port, string? database, string? username, string? password) + { + Host = host; + Port = port; + Database = database; + Username = username; + Password = password; + } + + /// + /// Creates new based on string in the format hostname:port:database:username:password. The : and \ characters should be escaped with a \. + /// + /// string for the entry from the pgpass file + /// New instance of for the string + /// Entry is not formatted as hostname:port:database:username:password or non-wildcard port is not a number + internal static Entry Parse(string serializedEntry) + { + var parts = Regex.Split(serializedEntry, @"(? part.Replace("\\:", ":").Replace("\\\\", "\\")) // unescape any escaped characters + .Select(part => part == PgPassWildcard ? null : part) + .ToArray(); + + int? port = null; + if (processedParts[1] != null) + { + if (!int.TryParse(processedParts[1], out var tempPort)) + throw new FormatException("pgpass entry was not formatted correctly. Port must be a valid integer."); + port = tempPort; + } + + return new Entry(processedParts[0], port, processedParts[2], processedParts[3], processedParts[4]); + } + + #endregion + + + /// + /// Checks whether this matches the parameters supplied + /// + /// Hostname to check against this entry + /// Port to check against this entry + /// Database to check against this entry + /// Username to check against this entry + /// True if the entry is a match. False otherwise. + internal bool IsMatch(string? host, int? port, string? database, string? username) => + AreValuesMatched(host, Host) && AreValuesMatched(port, Port) && AreValuesMatched(database, Database) && AreValuesMatched(username, Username); + + /// + /// Checks if 2 strings are a match for a considering that either value can be a wildcard (*) + /// + /// Value being searched + /// Value from the PGPASS entry + /// True if the values are a match. False otherwise. + bool AreValuesMatched(string? query, string? actual) + => query == actual || actual == null || query == null; + + bool AreValuesMatched(int? query, int? actual) + => query == actual || actual == null || query == null; + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/PoolManager.cs b/LibExternal/Npgsql/PoolManager.cs new file mode 100644 index 0000000..82b1c59 --- /dev/null +++ b/LibExternal/Npgsql/PoolManager.cs @@ -0,0 +1,130 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading; + +namespace Npgsql; + +/// +/// Provides lookup for a pool based on a connection string. +/// +/// +/// is lock-free, to avoid contention, but the same isn't +/// true of , which acquires a lock. The calling code always tries +/// before trying to . +/// +static class PoolManager +{ + internal const int InitialPoolsSize = 10; + + static readonly object Lock = new(); + static volatile (string Key, ConnectorSource Pool)[] _pools = new (string, ConnectorSource)[InitialPoolsSize]; + static volatile int _nextSlot; + + internal static (string Key, ConnectorSource Pool)[] Pools => _pools; + + internal static bool TryGetValue(string key, [NotNullWhen(true)] out ConnectorSource? pool) + { + // Note that pools never get removed. _pools is strictly append-only. + var nextSlot = _nextSlot; + var pools = _pools; + var sw = new SpinWait(); + + // First scan the pools and do reference equality on the connection strings + for (var i = 0; i < nextSlot; i++) + { + var cp = pools[i]; + if (ReferenceEquals(cp.Key, key)) + { + // It's possible that this pool entry is currently being written: the connection string + // component has already been written, but the pool component is just about to be. So we + // loop on the pool until it's non-null + while (Volatile.Read(ref cp.Pool) == null) + sw.SpinOnce(); + pool = cp.Pool; + return true; + } + } + + // Next try value comparison on the strings + for (var i = 0; i < nextSlot; i++) + { + var cp = pools[i]; + if (cp.Key == key) + { + // See comment above + while (Volatile.Read(ref cp.Pool) == null) + sw.SpinOnce(); + pool = cp.Pool; + return true; + } + } + + pool = null; + return false; + } + + internal static ConnectorSource GetOrAdd(string key, ConnectorSource pool) + { + lock (Lock) + { + if (TryGetValue(key, out var result)) + return result; + + // May need to grow the array. + if (_nextSlot == _pools.Length) + { + var newPools = new (string, ConnectorSource)[_pools.Length * 2]; + Array.Copy(_pools, newPools, _pools.Length); + _pools = newPools; + } + + _pools[_nextSlot].Key = key; + _pools[_nextSlot].Pool = pool; + Interlocked.Increment(ref _nextSlot); + return pool; + } + } + + internal static void Clear(string connString) + { + if (TryGetValue(connString, out var pool)) + pool.Clear(); + } + + internal static void ClearAll() + { + lock (Lock) + { + var pools = _pools; + for (var i = 0; i < _nextSlot; i++) + { + var cp = pools[i]; + if (cp.Key == null) + return; + cp.Pool?.Clear(); + } + } + } + + static PoolManager() + { + // When the appdomain gets unloaded (e.g. web app redeployment) attempt to nicely + // close idle connectors to prevent errors in PostgreSQL logs (#491). + AppDomain.CurrentDomain.DomainUnload += (sender, args) => ClearAll(); + AppDomain.CurrentDomain.ProcessExit += (sender, args) => ClearAll(); + } + + /// + /// Resets the pool manager to its initial state, for test purposes only. + /// Assumes that no other threads are accessing the pool. + /// + internal static void Reset() + { + lock (Lock) + { + ClearAll(); + _pools = new (string, ConnectorSource)[InitialPoolsSize]; + _nextSlot = 0; + } + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/PostgresDatabaseInfo.cs b/LibExternal/Npgsql/PostgresDatabaseInfo.cs new file mode 100644 index 0000000..d928207 --- /dev/null +++ b/LibExternal/Npgsql/PostgresDatabaseInfo.cs @@ -0,0 +1,547 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using Npgsql.BackendMessages; +using Npgsql.Internal; +using Npgsql.Logging; +using Npgsql.PostgresTypes; +using Npgsql.Util; +using static Npgsql.Util.Statics; + +// ReSharper disable StringLiteralTypo +// ReSharper disable CommentTypo + +namespace Npgsql; + +/// +/// The default implementation of , for standard PostgreSQL databases.. +/// +class PostgresDatabaseInfoFactory : INpgsqlDatabaseInfoFactory +{ + /// + public async Task Load(NpgsqlConnector conn, NpgsqlTimeout timeout, bool async) + { + var db = new PostgresDatabaseInfo(conn); + await db.LoadPostgresInfo(conn, timeout, async); + Debug.Assert(db.LongVersion != null); + return db; + } +} + +/// +/// The default implementation of NpgsqlDatabase, for standard PostgreSQL databases. +/// +class PostgresDatabaseInfo : NpgsqlDatabaseInfo +{ + /// + /// The Npgsql logger instance. + /// + static readonly NpgsqlLogger Log = NpgsqlLogManager.CreateLogger(nameof(PostgresDatabaseInfo)); + + /// + /// The PostgreSQL types detected in the database. + /// + List? _types; + + /// + protected override IEnumerable GetTypes() => _types ?? Enumerable.Empty(); + + /// + /// The PostgreSQL version string as returned by the version() function. Populated during loading. + /// + public string LongVersion { get; set; } = default!; + + /// + /// True if the backend is Amazon Redshift; otherwise, false. + /// + public bool IsRedshift { get; private set; } + + /// + public override bool SupportsUnlisten => Version.IsGreaterOrEqual(6, 4) && !IsRedshift; + + /// + /// True if the 'pg_enum' table includes the 'enumsortorder' column; otherwise, false. + /// + public virtual bool HasEnumSortOrder => Version.IsGreaterOrEqual(9, 1); + + /// + /// True if the 'pg_type' table includes the 'typcategory' column; otherwise, false. + /// + /// + /// pg_type.typcategory is added after 8.4. + /// see: https://www.postgresql.org/docs/8.4/static/catalog-pg-type.html#CATALOG-TYPCATEGORY-TABLE + /// + public virtual bool HasTypeCategory => Version.IsGreaterOrEqual(8, 4); + + internal PostgresDatabaseInfo(NpgsqlConnector conn) + : base(conn.Host!, conn.Port, conn.Database!, conn.PostgresParameters["server_version"]) + { + } + + /// + /// Loads database information from the PostgreSQL database specified by . + /// + /// The database connection. + /// The timeout while loading types from the backend. + /// True to load types asynchronously. + /// + /// A task representing the asynchronous operation. + /// + internal async Task LoadPostgresInfo(NpgsqlConnector conn, NpgsqlTimeout timeout, bool async) + { + HasIntegerDateTimes = + conn.PostgresParameters.TryGetValue("integer_datetimes", out var intDateTimes) && + intDateTimes == "on"; + + IsRedshift = conn.Settings.ServerCompatibilityMode == ServerCompatibilityMode.Redshift; + _types = await LoadBackendTypes(conn, timeout, async); + } + + /// + /// Generates a raw SQL query string to select type information. + /// + /// + /// Select all types (base, array which is also base, enum, range, composite). + /// Note that arrays are distinguished from primitive types through them having typreceive=array_recv. + /// Order by primitives first, container later. + /// For arrays and ranges, join in the element OID and type (to filter out arrays of unhandled + /// types). + /// + static string GenerateLoadTypesQuery(bool withRange, bool withMultirange, bool loadTableComposites) + => $@" +SELECT ns.nspname, t.oid, t.typname, t.typtype, t.typnotnull, t.elemtypoid +FROM ( + -- Arrays have typtype=b - this subquery identifies them by their typreceive and converts their typtype to a + -- We first do this for the type (innerest-most subquery), and then for its element type + -- This also returns the array element, range subtype and domain base type as elemtypoid + SELECT + typ.oid, typ.typnamespace, typ.typname, typ.typtype, typ.typrelid, typ.typnotnull, typ.relkind, + elemtyp.oid AS elemtypoid, elemtyp.typname AS elemtypname, elemcls.relkind AS elemrelkind, + CASE WHEN elemproc.proname='array_recv' THEN 'a' ELSE elemtyp.typtype END AS elemtyptype + FROM ( + SELECT typ.oid, typnamespace, typname, typrelid, typnotnull, relkind, typelem AS elemoid, + CASE WHEN proc.proname='array_recv' THEN 'a' ELSE typ.typtype END AS typtype, + CASE + WHEN proc.proname='array_recv' THEN typ.typelem + {(withRange ? "WHEN typ.typtype='r' THEN rngsubtype" : "")} + {(withMultirange ? "WHEN typ.typtype='m' THEN (SELECT rngtypid FROM pg_range WHERE rngmultitypid = typ.oid)" : "")} + WHEN typ.typtype='d' THEN typ.typbasetype + END AS elemtypoid + FROM pg_type AS typ + LEFT JOIN pg_class AS cls ON (cls.oid = typ.typrelid) + LEFT JOIN pg_proc AS proc ON proc.oid = typ.typreceive + {(withRange ? "LEFT JOIN pg_range ON (pg_range.rngtypid = typ.oid)" : "")} + ) AS typ + LEFT JOIN pg_type AS elemtyp ON elemtyp.oid = elemtypoid + LEFT JOIN pg_class AS elemcls ON (elemcls.oid = elemtyp.typrelid) + LEFT JOIN pg_proc AS elemproc ON elemproc.oid = elemtyp.typreceive +) AS t +JOIN pg_namespace AS ns ON (ns.oid = typnamespace) +WHERE + typtype IN ('b', 'r', 'm', 'e', 'd') OR -- Base, range, multirange, enum, domain + (typtype = 'c' AND {(loadTableComposites ? "ns.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')" : "relkind='c'")}) OR -- User-defined free-standing composites (not table composites) by default + (typtype = 'p' AND typname IN ('record', 'void')) OR -- Some special supported pseudo-types + (typtype = 'a' AND ( -- Array of... + elemtyptype IN ('b', 'r', 'm', 'e', 'd') OR -- Array of base, range, multirange, enum, domain + (elemtyptype = 'p' AND elemtypname IN ('record', 'void')) OR -- Arrays of special supported pseudo-types + (elemtyptype = 'c' AND {(loadTableComposites ? "ns.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')" : "elemrelkind='c'")}) -- Array of user-defined free-standing composites (not table composites) by default + )) +ORDER BY CASE + WHEN typtype IN ('b', 'e', 'p') THEN 0 -- First base types, enums, pseudo-types + WHEN typtype = 'r' THEN 1 -- Ranges after + WHEN typtype = 'm' THEN 2 -- Multiranges after + WHEN typtype = 'c' THEN 3 -- Composites after + WHEN typtype = 'd' AND elemtyptype <> 'a' THEN 4 -- Domains over non-arrays after + WHEN typtype = 'a' THEN 5 -- Arrays after + WHEN typtype = 'd' AND elemtyptype = 'a' THEN 6 -- Domains over arrays last +END;"; + + static string GenerateLoadCompositeTypesQuery(bool loadTableComposites) + => $@" +-- Load field definitions for (free-standing) composite types +SELECT typ.oid, att.attname, att.atttypid +FROM pg_type AS typ +JOIN pg_namespace AS ns ON (ns.oid = typ.typnamespace) +JOIN pg_class AS cls ON (cls.oid = typ.typrelid) +JOIN pg_attribute AS att ON (att.attrelid = typ.typrelid) +WHERE + (typ.typtype = 'c' AND {(loadTableComposites ? "ns.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')" : "cls.relkind='c'")}) AND + attnum > 0 AND -- Don't load system attributes + NOT attisdropped +ORDER BY typ.oid, att.attnum;"; + + static string GenerateLoadEnumFieldsQuery(bool withEnumSortOrder) + => $@" +-- Load enum fields +SELECT pg_type.oid, enumlabel +FROM pg_enum +JOIN pg_type ON pg_type.oid=enumtypid +ORDER BY oid{(withEnumSortOrder ? ", enumsortorder" : "")};"; + + /// + /// Loads type information from the backend specified by . + /// + /// The database connection. + /// The timeout while loading types from the backend. + /// True to load types asynchronously. + /// + /// A collection of types loaded from the backend. + /// + /// + /// Unknown typtype for type '{internalName}' in pg_type: {typeChar}. + internal async Task> LoadBackendTypes(NpgsqlConnector conn, NpgsqlTimeout timeout, bool async) + { + var commandTimeout = 0; // Default to infinity + if (timeout.IsSet) + commandTimeout = (int)timeout.CheckAndGetTimeLeft().TotalSeconds; + + var versionQuery = "SELECT version();"; + var loadTypesQuery = GenerateLoadTypesQuery(SupportsRangeTypes, SupportsMultirangeTypes, conn.Settings.LoadTableComposites); + var loadCompositeTypesQuery = GenerateLoadCompositeTypesQuery(conn.Settings.LoadTableComposites); + var loadEnumFieldsQuery = SupportsEnumTypes + ? GenerateLoadEnumFieldsQuery(HasEnumSortOrder) + : string.Empty; + + timeout.CheckAndApply(conn); + // The Lexer (https://github.com/postgres/postgres/blob/master/src/backend/replication/repl_scanner.l) + // and Parser (https://github.com/postgres/postgres/blob/master/src/backend/replication/repl_gram.y) + // for replication connections are pretty picky and somewhat flawed. + // Currently (2022-01-22) they do not support + // - SQL batches containing multiple commands + // - The ('\r') in Windows or Mac newlines + // - Comments + // For this reason we need clean up our type loading queries for replication connections and execute + // them individually instead of batched. + // Theoretically we cold even use the extended protocol + batching for regular (non-replication) + // connections but that would branch our code even more for very little gain. + var isReplicationConnection = conn.Settings.ReplicationMode != ReplicationMode.Off; + if (isReplicationConnection) + { + await conn.WriteQuery(versionQuery, async); + await conn.WriteQuery(SanitizeForReplicationConnection(loadTypesQuery), async); + await conn.WriteQuery(SanitizeForReplicationConnection(loadCompositeTypesQuery), async); + if (SupportsEnumTypes) + await conn.WriteQuery(SanitizeForReplicationConnection(loadEnumFieldsQuery), async); + + static string SanitizeForReplicationConnection(string str) + { + var sb = new StringBuilder(str.Length); + using var c = str.GetEnumerator(); + while (c.MoveNext()) + { + switch (c.Current) + { + case '\r': + sb.Append('\n'); + // Check for a \n after the \r + // and swallow it if it exists + if (c.MoveNext()) + { + if (c.Current == '-') + goto case '-'; + if (c.Current != '\n') + sb.Append(c.Current); + } + break; + case '-': + // Check if there is a second dash + if (c.MoveNext()) + { + if (c.Current == '\r') + { + sb.Append('-'); + goto case '\r'; + } + if (c.Current != '-') + { + sb.Append('-'); + sb.Append(c.Current); + break; + } + + // Comment mode + // Swallow everything until we find a newline + while (c.MoveNext()) + { + if (c.Current == '\r') + goto case '\r'; + if (c.Current == '\n') + { + sb.Append('\n'); + break; + } + } + } + break; + default: + sb.Append(c.Current); + break; + } + } + + return sb.ToString(); + } + } + else + { + var batchQuery = new StringBuilder( + versionQuery.Length + + loadTypesQuery.Length + + loadCompositeTypesQuery.Length + + (SupportsEnumTypes + ? loadEnumFieldsQuery.Length + : 0)) + .AppendLine(versionQuery) + .AppendLine(loadTypesQuery) + .AppendLine(loadCompositeTypesQuery); + + if (SupportsEnumTypes) + batchQuery.AppendLine(loadEnumFieldsQuery); + await conn.WriteQuery(batchQuery.ToString(), async); + } + await conn.Flush(async); + var byOID = new Dictionary(); + var buf = conn.ReadBuffer; + + // First read the PostgreSQL version + Expect(await conn.ReadMessage(async), conn); + + // We read the message in non-sequential mode which buffers the whole message. + // There is no need to ensure data within the message boundaries + Expect(await conn.ReadMessage(async), conn); + buf.Skip(2); // Column count + LongVersion = ReadNonNullableString(buf); + Expect(await conn.ReadMessage(async), conn); + if (isReplicationConnection) + Expect(await conn.ReadMessage(async), conn); + + // Then load the types + Expect(await conn.ReadMessage(async), conn); + IBackendMessage msg; + while (true) + { + msg = await conn.ReadMessage(async); + if (msg is not DataRowMessage) + break; + + buf.Skip(2); // Column count + var nspname = ReadNonNullableString(buf); + var oid = uint.Parse(ReadNonNullableString(buf), NumberFormatInfo.InvariantInfo); + Debug.Assert(oid != 0); + var typname = ReadNonNullableString(buf); + var typtype = ReadNonNullableString(buf)[0]; + var typnotnull = ReadNonNullableString(buf)[0] == 't'; + var len = buf.ReadInt32(); + var elemtypoid = len == -1 ? 0 : uint.Parse(buf.ReadString(len), NumberFormatInfo.InvariantInfo); + + switch (typtype) + { + case 'b': // Normal base type + var baseType = new PostgresBaseType(nspname, typname, oid); + byOID[baseType.OID] = baseType; + continue; + + case 'a': // Array + { + Debug.Assert(elemtypoid > 0); + if (!byOID.TryGetValue(elemtypoid, out var elementPostgresType)) + { + Log.Trace($"Array type '{typname}' refers to unknown element with OID {elemtypoid}, skipping", conn.Id); + continue; + } + + var arrayType = new PostgresArrayType(nspname, typname, oid, elementPostgresType); + byOID[arrayType.OID] = arrayType; + continue; + } + + case 'r': // Range + { + Debug.Assert(elemtypoid > 0); + if (!byOID.TryGetValue(elemtypoid, out var subtypePostgresType)) + { + Log.Trace($"Range type '{typname}' refers to unknown subtype with OID {elemtypoid}, skipping", conn.Id); + continue; + } + + var rangeType = new PostgresRangeType(nspname, typname, oid, subtypePostgresType); + byOID[rangeType.OID] = rangeType; + continue; + } + + case 'm': // Multirange + Debug.Assert(elemtypoid > 0); + if (!byOID.TryGetValue(elemtypoid, out var type)) + { + Log.Trace($"Multirange type '{typname}' refers to unknown range with OID {elemtypoid}, skipping", conn.Id); + continue; + } + + if (type is not PostgresRangeType rangePostgresType) + { + Log.Trace($"Multirange type '{typname}' refers to non-range type {type.Name}, skipping", + conn.Id); + continue; + } + + var multirangeType = new PostgresMultirangeType(nspname, typname, oid, rangePostgresType); + byOID[multirangeType.OID] = multirangeType; + continue; + + case 'e': // Enum + var enumType = new PostgresEnumType(nspname, typname, oid); + byOID[enumType.OID] = enumType; + continue; + + case 'c': // Composite + var compositeType = new PostgresCompositeType(nspname, typname, oid); + byOID[compositeType.OID] = compositeType; + continue; + + case 'd': // Domain + Debug.Assert(elemtypoid > 0); + if (!byOID.TryGetValue(elemtypoid, out var basePostgresType)) + { + Log.Trace($"Domain type '{typname}' refers to unknown base type with OID {elemtypoid}, skipping", conn.Id); + continue; + } + + var domainType = new PostgresDomainType(nspname, typname, oid, basePostgresType, typnotnull); + byOID[domainType.OID] = domainType; + continue; + + case 'p': // pseudo-type (record, void) + goto case 'b'; // Hack this as a base type + + default: + throw new ArgumentOutOfRangeException($"Unknown typtype for type '{typname}' in pg_type: {typtype}"); + } + } + Expect(msg, conn); + if (isReplicationConnection) + Expect(await conn.ReadMessage(async), conn); + + // Then load the composite type fields + Expect(await conn.ReadMessage(async), conn); + + var currentOID = uint.MaxValue; + PostgresCompositeType? currentComposite = null; + var skipCurrent = false; + + while (true) + { + msg = await conn.ReadMessage(async); + if (msg is not DataRowMessage) + break; + + buf.Skip(2); // Column count + var oid = uint.Parse(ReadNonNullableString(buf), NumberFormatInfo.InvariantInfo); + var attname = ReadNonNullableString(buf); + var atttypid = uint.Parse(ReadNonNullableString(buf), NumberFormatInfo.InvariantInfo); + + if (oid != currentOID) + { + currentOID = oid; + + if (!byOID.TryGetValue(oid, out var type)) // See #2020 + { + Log.Warn($"Skipping composite type with OID {oid} which was not found in pg_type"); + byOID.Remove(oid); + skipCurrent = true; + continue; + } + + currentComposite = type as PostgresCompositeType; + if (currentComposite == null) + { + Log.Warn($"Type {type.Name} was referenced as a composite type but is a {type.GetType()}"); + byOID.Remove(oid); + skipCurrent = true; + continue; + } + + skipCurrent = false; + } + + if (skipCurrent) + continue; + + if (!byOID.TryGetValue(atttypid, out var fieldType)) // See #2020 + { + Log.Warn($"Skipping composite type {currentComposite!.DisplayName} with field {attname} with type OID {atttypid}, which could not be resolved to a PostgreSQL type."); + byOID.Remove(oid); + skipCurrent = true; + continue; + } + + currentComposite!.MutableFields.Add(new PostgresCompositeType.Field(attname, fieldType)); + } + Expect(msg, conn); + if (isReplicationConnection) + Expect(await conn.ReadMessage(async), conn); + + if (SupportsEnumTypes) + { + // Then load the enum fields + Expect(await conn.ReadMessage(async), conn); + + currentOID = uint.MaxValue; + PostgresEnumType? currentEnum = null; + skipCurrent = false; + + while (true) + { + msg = await conn.ReadMessage(async); + if (msg is not DataRowMessage) + break; + + buf.Skip(2); // Column count + var oid = uint.Parse(ReadNonNullableString(buf), NumberFormatInfo.InvariantInfo); + var enumlabel = ReadNonNullableString(buf); + if (oid != currentOID) + { + currentOID = oid; + + if (!byOID.TryGetValue(oid, out var type)) // See #2020 + { + Log.Warn($"Skipping enum type with OID {oid} which was not found in pg_type"); + byOID.Remove(oid); + skipCurrent = true; + continue; + } + + currentEnum = type as PostgresEnumType; + if (currentEnum == null) + { + Log.Warn($"Type {type.Name} was referenced as an enum type but is a {type.GetType()}"); + byOID.Remove(oid); + skipCurrent = true; + continue; + } + + skipCurrent = false; + } + + if (skipCurrent) + continue; + + currentEnum!.MutableLabels.Add(enumlabel); + } + Expect(msg, conn); + if (isReplicationConnection) + Expect(await conn.ReadMessage(async), conn); + } + + if (!isReplicationConnection) + Expect(await conn.ReadMessage(async), conn); + return byOID.Values.ToList(); + + static string ReadNonNullableString(NpgsqlReadBuffer buffer) + => buffer.ReadString(buffer.ReadInt32()); + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/PostgresEnvironment.cs b/LibExternal/Npgsql/PostgresEnvironment.cs new file mode 100644 index 0000000..6903660 --- /dev/null +++ b/LibExternal/Npgsql/PostgresEnvironment.cs @@ -0,0 +1,58 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Npgsql; + +static class PostgresEnvironment +{ + internal static string? User => Environment.GetEnvironmentVariable("PGUSER"); + + internal static string? Password => Environment.GetEnvironmentVariable("PGPASSWORD"); + + internal static string? PassFile => Environment.GetEnvironmentVariable("PGPASSFILE"); + + internal static string? PassFileDefault + => (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? GetHomePostgresDir() : GetHomeDir()) is string homedir && + Path.Combine(homedir, RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "pgpass.conf" : ".pgpass") is var path && + File.Exists(path) + ? path + : null; + + internal static string? SslCert => Environment.GetEnvironmentVariable("PGSSLCERT"); + + internal static string? SslCertDefault + => GetHomePostgresDir() is string homedir && Path.Combine(homedir, "postgresql.crt") is var path && File.Exists(path) + ? path + : null; + + internal static string? SslKey => Environment.GetEnvironmentVariable("PGSSLKEY"); + + internal static string? SslKeyDefault + => GetHomePostgresDir() is string homedir && Path.Combine(homedir, "postgresql.key") is var path && File.Exists(path) + ? path + : null; + + internal static string? SslCertRoot => Environment.GetEnvironmentVariable("PGSSLROOTCERT"); + + internal static string? SslCertRootDefault + => GetHomePostgresDir() is string homedir && Path.Combine(homedir, "root.crt") is var path && File.Exists(path) + ? path + : null; + + internal static string? ClientEncoding => Environment.GetEnvironmentVariable("PGCLIENTENCODING"); + + internal static string? TimeZone => Environment.GetEnvironmentVariable("PGTZ"); + + internal static string? Options => Environment.GetEnvironmentVariable("PGOPTIONS"); + + internal static string? TargetSessionAttributes => Environment.GetEnvironmentVariable("PGTARGETSESSIONATTRS"); + + static string? GetHomeDir() + => Environment.GetEnvironmentVariable(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "APPDATA" : "HOME"); + + static string? GetHomePostgresDir() + => GetHomeDir() is string homedir + ? Path.Combine(homedir, RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "postgresql" : ".postgresql") + : null; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/PostgresErrorCodes.cs b/LibExternal/Npgsql/PostgresErrorCodes.cs new file mode 100644 index 0000000..4dbbf90 --- /dev/null +++ b/LibExternal/Npgsql/PostgresErrorCodes.cs @@ -0,0 +1,482 @@ +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +using System; +using System.Linq; + +namespace Npgsql; + +/// +/// Provides constants for PostgreSQL error codes. +/// +/// +/// See https://www.postgresql.org/docs/current/static/errcodes-appendix.html +/// +public static class PostgresErrorCodes +{ + #region Class 00 - Successful Completion + + public const string SuccessfulCompletion = "00000"; + + #endregion Class 00 - Successful Completion + + #region Class 01 - Warning + + public const string Warning = "01000"; + public const string DynamicResultSetsReturnedWarning = "0100C"; + public const string ImplicitZeroBitPaddingWarning = "01008"; + public const string NullValueEliminatedInSetFunctionWarning = "01003"; + public const string PrivilegeNotGrantedWarning = "01007"; + public const string PrivilegeNotRevokedWarning = "01006"; + public const string StringDataRightTruncationWarning = "01004"; + public const string DeprecatedFeatureWarning = "01P01"; + + #endregion Class 01 - Warning + + #region Class 02 - No Data + + public const string NoData = "02000"; + public const string NoAdditionalDynamicResultSetsReturned = "02001"; + + #endregion Class 02 - No Data + + #region Class 03 - SQL Statement Not Yet Complete + + public const string SqlStatementNotYetComplete = "03000"; + + #endregion Class 03 - SQL Statement Not Yet Complete + + #region Class 08 - Connection Exception + + public const string ConnectionException = "08000"; + public const string ConnectionDoesNotExist = "08003"; + public const string ConnectionFailure = "08006"; + public const string SqlClientUnableToEstablishSqlConnection = "08001"; + public const string SqlServerRejectedEstablishmentOfSqlConnection = "08004"; + public const string TransactionResolutionUnknown = "08007"; + public const string ProtocolViolation = "08P01"; + + #endregion Class 08 - Connection Exception + + #region Class 09 - Triggered Action Exception + + public const string TriggeredActionException = "09000"; + + #endregion Class 09 - Triggered Action Exception + + #region Class 0A - Feature Not Supported + + public const string FeatureNotSupported = "0A000"; + + #endregion Class 0A - Feature Not Supported + + #region Class 0B - Invalid Transaction Initiation + + public const string InvalidTransactionInitiation = "0B000"; + + #endregion Class 0B - Invalid Transaction Initiation + + #region Class 0F - Locator Exception + + public const string LocatorException = "0F000"; + public const string InvalidLocatorSpecification = "0F001"; + + #endregion Class 0F - Locator Exception + + #region Class 0L - Invalid Grantor + + public const string InvalidGrantor = "0L000"; + public const string InvalidGrantOperation = "0LP01"; + + #endregion Class 0L - Invalid Grantor + + #region Class 0P - Invalid Role Specification + + public const string InvalidRoleSpecification = "0P000"; + + #endregion Class 0P - Invalid Role Specification + + #region Class 0Z - Diagnostics Exception + + public const string DiagnosticsException = "0Z000"; + public const string StackedDiagnosticsAccessedWithoutActiveHandler = "0Z002"; + + #endregion Class 0Z - Diagnostics Exception + + #region Class 20 - Case Not Found + + public const string CaseNotFound = "20000"; + + #endregion Class 20 - Case Not Found + + #region Class 21 - CardinalityViolation + + public const string CardinalityViolation = "21000"; + + #endregion Class 21 - CardinalityViolation + + #region Class 22 - Data Exception + + public const string DataException = "22000"; + public const string ArraySubscriptError = "2202E"; + public const string CharacterNotInRepertoire = "22021"; + public const string DatetimeFieldOverflow = "22008"; + public const string DivisionByZero = "22012"; + public const string ErrorInAssignment = "22005"; + public const string EscapeCharacterConflict = "2200B"; + public const string IndicatorOverflow = "22022"; + public const string IntervalFieldOverflow = "22015"; + public const string InvalidArgumentForLogarithm = "2201E"; + public const string InvalidArgumentForNtileFunction = "22014"; + public const string InvalidArgumentForNthValueFunction = "22016"; + public const string InvalidArgumentForPowerFunction = "2201F"; + public const string InvalidArgumentForWidthBucketFunction = "2201G"; + public const string InvalidCharacterValueForCast = "22018"; + public const string InvalidDatetimeFormat = "22007"; + public const string InvalidEscapeCharacter = "22019"; + public const string InvalidEscapeOctet = "2200D"; + public const string InvalidEscapeSequence = "22025"; + public const string NonstandardUseOfEscapeCharacter = "22P06"; + public const string InvalidIndicatorParameterValue = "22010"; + public const string InvalidParameterValue = "22023"; + public const string InvalidRegularExpression = "2201B"; + public const string InvalidRowCountInLimitClause = "2201W"; + public const string InvalidRowCountInResultOffsetClause = "2201X"; + public const string InvalidTablesampleArgument = "2202H"; + public const string InvalidTablesampleRepeat = "2202G"; + public const string InvalidTimeZoneDisplacementValue = "22009"; + public const string InvalidUseOfEscapeCharacter = "2200C"; + public const string MostSpecificTypeMismatch = "2200G"; + public const string NullValueNotAllowed = "22004"; + public const string NullValueNoIndicatorParameter = "22002"; + public const string NumericValueOutOfRange = "22003"; + public const string StringDataLengthMismatch = "22026"; + public const string StringDataRightTruncation = "22001"; + public const string SubstringError = "22011"; + public const string TrimError = "22027"; + public const string UnterminatedCString = "22024"; + public const string ZeroLengthCharacterString = "2200F"; + public const string FloatingPointException = "22P01"; + public const string InvalidTextRepresentation = "22P02"; + public const string InvalidBinaryRepresentation = "22P03"; + public const string BadCopyFileFormat = "22P04"; + public const string UntranslatableCharacter = "22P05"; + public const string NotAnXmlDocument = "2200L"; + public const string InvalidXmlDocument = "2200M"; + public const string InvalidXmlContent = "2200N"; + public const string InvalidXmlComment = "2200S"; + public const string InvalidXmlProcessingInstruction = "2200T"; + + #endregion Class 22 - Data Exception + + #region Class 23 - Integrity Constraint Violation + + public const string IntegrityConstraintViolation = "23000"; + public const string RestrictViolation = "23001"; + public const string NotNullViolation = "23502"; + public const string ForeignKeyViolation = "23503"; + public const string UniqueViolation = "23505"; + public const string CheckViolation = "23514"; + public const string ExclusionViolation = "23P01"; + + #endregion Class 23 - Integrity Constraint Violation + + #region Class 24 - Invalid Cursor State + + public const string InvalidCursorState = "24000"; + + #endregion Class 24 - Invalid Cursor State + + #region Class 25 - Invalid Transaction State + + public const string InvalidTransactionState = "25000"; + public const string ActiveSqlTransaction = "25001"; + public const string BranchTransactionAlreadyActive = "25002"; + public const string HeldCursorRequiresSameIsolationLevel = "25008"; + public const string InappropriateAccessModeForBranchTransaction = "25003"; + public const string InappropriateIsolationLevelForBranchTransaction = "25004"; + public const string NoActiveSqlTransactionForBranchTransaction = "25005"; + public const string ReadOnlySqlTransaction = "25006"; + public const string SchemaAndDataStatementMixingNotSupported = "25007"; + public const string NoActiveSqlTransaction = "25P01"; + public const string InFailedSqlTransaction = "25P02"; + + #endregion Class 25 - Invalid Transaction State + + #region Class 26 - Invalid SQL Statement Name + + public const string InvalidSqlStatementName = "26000"; + + #endregion Class 26 - Invalid SQL Statement Name + + #region Class 27 - Triggered Data Change Violation + + public const string TriggeredDataChangeViolation = "27000"; + + #endregion Class 27 - Triggered Data Change Violation + + #region Class 28 - Invalid Authorization Scheme + + public const string InvalidAuthorizationSpecification = "28000"; + public const string InvalidPassword = "28P01"; + + #endregion Class 28 - Invalid Authorization Scheme + + #region Class 2B - Dependent Privilege Descriptors Still Exist + + public const string DependentPrivilegeDescriptorsStillExist = "2B000"; + public const string DependentObjectsStillExist = "2BP01"; + + #endregion Class 2B - Dependent Privilege Descriptors Still Exist + + #region Class 2D - Invalid Transaction Termination + + public const string InvalidTransactionTermination = "2D000"; + + #endregion Class 2D - Invalid Transaction Termination + + #region Class 2F - SQL Routine Exception + + public const string SqlRoutineException = "2F000"; + public const string FunctionExecutedNoReturnStatementSqlRoutineException = "2F005"; + public const string ModifyingSqlDataNotPermittedSqlRoutineException = "2F002"; + public const string ProhibitedSqlStatementAttemptedSqlRoutineException = "2F003"; + public const string ReadingSqlDataNotPermittedSqlRoutineException = "2F004"; + + #endregion Class 2F - SQL Routine Exception + + #region Class 34 - Invalid Cursor Name + + public const string InvalidCursorName = "34000"; + + #endregion Class 34 - Invalid Cursor Name + + #region Class 38 - External Routine Exception + + public const string ExternalRoutineException = "38000"; + public const string ContainingSqlNotPermittedExternalRoutineException = "38001"; + public const string ModifyingSqlDataNotPermittedExternalRoutineException = "38002"; + public const string ProhibitedSqlStatementAttemptedExternalRoutineException = "38003"; + public const string ReadingSqlDataNotPermittedExternalRoutineException = "38004"; + + #endregion Class 38 - External Routine Exception + + #region Class 39 - External Routine Invocation Exception + + public const string ExternalRoutineInvocationException = "39000"; + public const string InvalidSqlstateReturnedExternalRoutineInvocationException = "39001"; + public const string NullValueNotAllowedExternalRoutineInvocationException = "39004"; + public const string TriggerProtocolViolatedExternalRoutineInvocationException = "39P01"; + public const string SrfProtocolViolatedExternalRoutineInvocationException = "39P02"; + public const string EventTriggerProtocolViolatedExternalRoutineInvocationException = "39P03"; + + #endregion Class 39 - External Routine Invocation Exception + + #region Class 3B - Savepoint Exception + + public const string SavepointException = "3B000"; + public const string InvalidSavepointSpecification = "3B001"; + + #endregion Class 3B - Savepoint Exception + + #region Class 3D - Invalid Catalog Name + + public const string InvalidCatalogName = "3D000"; + + #endregion Class 3D - Invalid Catalog Name + + #region Class 3F - Invalid Schema Name + + public const string InvalidSchemaName = "3F000"; + + #endregion Class 3F - Invalid Schema Name + + #region Class 40 - Transaction Rollback + + public const string TransactionRollback = "40000"; + public const string TransactionIntegrityConstraintViolation = "40002"; + public const string SerializationFailure = "40001"; + public const string StatementCompletionUnknown = "40003"; + public const string DeadlockDetected = "40P01"; + + #endregion Class 40 - Transaction Rollback + + #region Class 42 - Syntax Error or Access Rule Violation + + public const string SyntaxErrorOrAccessRuleViolation = "42000"; + public const string SyntaxError = "42601"; + public const string InsufficientPrivilege = "42501"; + public const string CannotCoerce = "42846"; + public const string GroupingError = "42803"; + public const string WindowingError = "42P20"; + public const string InvalidRecursion = "42P19"; + public const string InvalidForeignKey = "42830"; + public const string InvalidName = "42602"; + public const string NameTooLong = "42622"; + public const string ReservedName = "42939"; + public const string DatatypeMismatch = "42804"; + public const string IndeterminateDatatype = "42P18"; + public const string CollationMismatch = "42P21"; + public const string IndeterminateCollation = "42P22"; + public const string WrongObjectType = "42809"; + public const string UndefinedColumn = "42703"; + public const string UndefinedFunction = "42883"; + public const string UndefinedTable = "42P01"; + public const string UndefinedParameter = "42P02"; + public const string UndefinedObject = "42704"; + public const string DuplicateColumn = "42701"; + public const string DuplicateCursor = "42P03"; + public const string DuplicateDatabase = "42P04"; + public const string DuplicateFunction = "42723"; + public const string DuplicatePreparedStatement = "42P05"; + public const string DuplicateSchema = "42P06"; + public const string DuplicateTable = "42P07"; + public const string DuplicateAlias = "42712"; + public const string DuplicateObject = "42710"; + public const string AmbiguousColumn = "42702"; + public const string AmbiguousFunction = "42725"; + public const string AmbiguousParameter = "42P08"; + public const string AmbiguousAlias = "42P09"; + public const string InvalidColumnReference = "42P10"; + public const string InvalidColumnDefinition = "42611"; + public const string InvalidCursorDefinition = "42P11"; + public const string InvalidDatabaseDefinition = "42P12"; + public const string InvalidFunctionDefinition = "42P13"; + public const string InvalidPreparedStatementDefinition = "42P14"; + public const string InvalidSchemaDefinition = "42P15"; + public const string InvalidTableDefinition = "42P16"; + public const string InvalidObjectDefinition = "42P17"; + + #endregion Class 42 - Syntax Error or Access Rule Violation + + #region Class 44 - WITH CHECK OPTION Violation + + public const string WithCheckOptionViolation = "44000"; + + #endregion Class 44 - WITH CHECK OPTION Violation + + #region Class 53 - Insufficient Resources + + public const string InsufficientResources = "53000"; + public const string DiskFull = "53100"; + public const string OutOfMemory = "53200"; + public const string TooManyConnections = "53300"; + public const string ConfigurationLimitExceeded = "53400"; + + #endregion Class 53 - Insufficient Resources + + #region Class 54 - Program Limit Exceeded + + public const string ProgramLimitExceeded = "54000"; + public const string StatementTooComplex = "54001"; + public const string TooManyColumns = "54011"; + public const string TooManyArguments = "54023"; + + #endregion Class 54 - Program Limit Exceeded + + #region Class 55 - Object Not In Prerequisite State + + public const string ObjectNotInPrerequisiteState = "55000"; + public const string ObjectInUse = "55006"; + public const string CantChangeRuntimeParam = "55P02"; + public const string LockNotAvailable = "55P03"; + + #endregion Class 55 - Object Not In Prerequisite State + + #region Class 57 - Operator Intervention + + public const string OperatorIntervention = "57000"; + public const string QueryCanceled = "57014"; + public const string AdminShutdown = "57P01"; + public const string CrashShutdown = "57P02"; + public const string CannotConnectNow = "57P03"; + public const string DatabaseDropped = "57P04"; + + #endregion Class 57 - Operator Intervention + + #region Class 58 - System Error (errors external to PostgreSQL itself) + + public const string SystemError = "58000"; + public const string IoError = "58030"; + public const string UndefinedFile = "58P01"; + public const string DuplicateFile = "58P02"; + + #endregion Class 58 - System Error (errors external to PostgreSQL itself) + + #region Class 72 - Snapshot Failure + + public const string SnapshotFailure = "72000"; + + #endregion Class 72 - Snapshot Failure + + #region Class F0 - Configuration File Error + + public const string ConfigFileError = "F0000"; + public const string LockFileExists = "F0001"; + + #endregion Class F0 - Configuration File Error + + #region Class HV - Foreign Data Wrapper Error (SQL/MED) + + public const string FdwError = "HV000"; + public const string FdwColumnNameNotFound = "HV005"; + public const string FdwDynamicParameterValueNeeded = "HV002"; + public const string FdwFunctionSequenceError = "HV010"; + public const string FdwInconsistentDescriptorInformation = "HV021"; + public const string FdwInvalidAttributeValue = "HV024"; + public const string FdwInvalidColumnName = "HV007"; + public const string FdwInvalidColumnNumber = "HV008"; + public const string FdwInvalidDataType = "HV004"; + public const string FdwInvalidDataTypeDescriptors = "HV006"; + public const string FdwInvalidDescriptorFieldIdentifier = "HV091"; + public const string FdwInvalidHandle = "HV00B"; + public const string FdwInvalidOptionIndex = "HV00C"; + public const string FdwInvalidOptionName = "HV00D"; + public const string FdwInvalidStringLengthOrBufferLength = "HV090"; + public const string FdwInvalidStringFormat = "HV00A"; + public const string FdwInvalidUseOfNullPointer = "HV009"; + public const string FdwTooManyHandles = "HV014"; + public const string FdwOutOfMemory = "HV001"; + public const string FdwNoSchemas = "HV00P"; + public const string FdwOptionNameNotFound = "HV00J"; + public const string FdwReplyHandle = "HV00K"; + public const string FdwSchemaNotFound = "HV00Q"; + public const string FdwTableNotFound = "HV00R"; + public const string FdwUnableToCreateExecution = "HV00L"; + public const string FdwUnableToCreateReply = "HV00M"; + public const string FdwUnableToEstablishConnection = "HV00N"; + + #endregion Class HV - Foreign Data Wrapper Error (SQL/MED) + + #region Class P0 - PL/pgSQL Error + + public const string PlpgsqlError = "P0000"; + public const string RaiseException = "P0001"; + public const string NoDataFound = "P0002"; + public const string TooManyRows = "P0003"; + public const string AssertFailure = "P0004"; + + #endregion Class P0 - PL/pgSQL Error + + #region Class XX - Internal Error + + public const string InternalError = "XX000"; + public const string DataCorrupted = "XX001"; + public const string IndexCorrupted = "XX002"; + + #endregion Class XX - Internal Error + + static readonly string[] CriticalFailureCodes = + { + "53", // Insufficient resources + AdminShutdown, // Self explanatory + CrashShutdown, // Self explanatory + CannotConnectNow, // Database is starting up + "58", // System errors, external to PG (server is dying) + "F0", // Configuration file error + "XX", // Internal error (database is dying) + }; + + internal static bool IsCriticalFailure(PostgresException e, bool clusterError = true) + => CriticalFailureCodes.Any(x => e.SqlState.StartsWith(x, StringComparison.Ordinal)) || + !clusterError && e.SqlState == ProtocolViolation; // We only treat ProtocolViolation as critical for connection +} \ No newline at end of file diff --git a/LibExternal/Npgsql/PostgresException.cs b/LibExternal/Npgsql/PostgresException.cs new file mode 100644 index 0000000..e1a042e --- /dev/null +++ b/LibExternal/Npgsql/PostgresException.cs @@ -0,0 +1,373 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.Text; +using Npgsql.BackendMessages; +using Npgsql.Internal; + +namespace Npgsql; + +/// +/// The exception that is thrown when the PostgreSQL backend reports errors (e.g. query +/// SQL issues, constraint violations). +/// +/// +/// This exception only corresponds to a PostgreSQL-delivered error. +/// Other errors (e.g. network issues) will be raised via , +/// and purely Npgsql-related issues which aren't related to the server will be raised +/// via the standard CLR exceptions (e.g. ). +/// +/// See https://www.postgresql.org/docs/current/static/errcodes-appendix.html, +/// https://www.postgresql.org/docs/current/static/protocol-error-fields.html +/// +[Serializable] +public sealed class PostgresException : NpgsqlException +{ + /// + /// Creates a new instance. + /// + public PostgresException(string messageText, string severity, string invariantSeverity, string sqlState) + : this(messageText, severity, invariantSeverity, sqlState, detail: null) {} + + /// + /// Creates a new instance. + /// + public PostgresException( + string messageText, string severity, string invariantSeverity, string sqlState, + string? detail = null, string? hint = null, int position = 0, int internalPosition = 0, + string? internalQuery = null, string? where = null, string? schemaName = null, string? tableName = null, + string? columnName = null, string? dataTypeName = null, string? constraintName = null, string? file = null, + string? line = null, string? routine = null) + : base(GetMessage(sqlState, messageText, position, detail)) + { + MessageText = messageText; + Severity = severity; + InvariantSeverity = invariantSeverity; + SqlState = sqlState; + + Detail = detail; + Hint = hint; + Position = position; + InternalPosition = internalPosition; + InternalQuery = internalQuery; + Where = where; + SchemaName = schemaName; + TableName = tableName; + ColumnName = columnName; + DataTypeName = dataTypeName; + ConstraintName = constraintName; + File = file; + Line = line; + Routine = routine; + + AddData(nameof(Severity), Severity); + AddData(nameof(InvariantSeverity), InvariantSeverity); + AddData(nameof(SqlState), SqlState); + AddData(nameof(MessageText), MessageText); + AddData(nameof(Detail), Detail); + AddData(nameof(Hint), Hint); + AddData(nameof(Position), Position); + AddData(nameof(InternalPosition), InternalPosition); + AddData(nameof(InternalQuery), InternalQuery); + AddData(nameof(Where), Where); + AddData(nameof(SchemaName), SchemaName); + AddData(nameof(TableName), TableName); + AddData(nameof(ColumnName), ColumnName); + AddData(nameof(DataTypeName), DataTypeName); + AddData(nameof(ConstraintName), ConstraintName); + AddData(nameof(File), File); + AddData(nameof(Line), Line); + AddData(nameof(Routine), Routine); + + void AddData(string key, T value) + { + if (!EqualityComparer.Default.Equals(value, default!)) + Data.Add(key, value); + } + } + + static string GetMessage(string sqlState, string messageText, int position, string? detail) + { + var baseMessage = sqlState + ": " + messageText; + var additionalMessage = + TryAddString("POSITION", position == 0 ? null : position.ToString()) + + TryAddString("DETAIL", detail); + return string.IsNullOrEmpty(additionalMessage) + ? baseMessage + : baseMessage + Environment.NewLine + additionalMessage; + } + + static string TryAddString(string text, string? value) => !string.IsNullOrWhiteSpace(value) ? $"{Environment.NewLine}{text}: {value}" : string.Empty; + + PostgresException(ErrorOrNoticeMessage msg) + : this( + msg.Message, msg.Severity, msg.InvariantSeverity, msg.SqlState, + msg.Detail, msg.Hint, msg.Position, msg.InternalPosition, msg.InternalQuery, + msg.Where, msg.SchemaName, msg.TableName, msg.ColumnName, msg.DataTypeName, + msg.ConstraintName, msg.File, msg.Line, msg.Routine) {} + + internal static PostgresException Load(NpgsqlReadBuffer buf, bool includeDetail) + => new(ErrorOrNoticeMessage.Load(buf, includeDetail)); + + internal PostgresException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + Severity = GetValue(nameof(Severity)); + InvariantSeverity = GetValue(nameof(InvariantSeverity)); + SqlState = GetValue(nameof(SqlState)); + MessageText = GetValue(nameof(MessageText)); + Detail = GetValue(nameof(Detail)); + Hint = GetValue(nameof(Hint)); + Position = GetValue(nameof(Position)); + InternalPosition = GetValue(nameof(InternalPosition)); + InternalQuery = GetValue(nameof(InternalQuery)); + Where = GetValue(nameof(Where)); + SchemaName = GetValue(nameof(SchemaName)); + TableName = GetValue(nameof(TableName)); + ColumnName = GetValue(nameof(ColumnName)); + DataTypeName = GetValue(nameof(DataTypeName)); + ConstraintName = GetValue(nameof(ConstraintName)); + File = GetValue(nameof(File)); + Line = GetValue(nameof(Line)); + Routine = GetValue(nameof(Routine)); + + T GetValue(string propertyName) => (T)info.GetValue(propertyName, typeof(T))!; + } + + /// + /// Populates a with the data needed to serialize the target object. + /// + /// The to populate with data. + /// The destination (see ) for this serialization. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + info.AddValue(nameof(Severity), Severity); + info.AddValue(nameof(InvariantSeverity), InvariantSeverity); + info.AddValue(nameof(SqlState), SqlState); + info.AddValue(nameof(MessageText), MessageText); + info.AddValue(nameof(Detail), Detail); + info.AddValue(nameof(Hint), Hint); + info.AddValue(nameof(Position), Position); + info.AddValue(nameof(InternalPosition), InternalPosition); + info.AddValue(nameof(InternalQuery), InternalQuery); + info.AddValue(nameof(Where), Where); + info.AddValue(nameof(SchemaName), SchemaName); + info.AddValue(nameof(TableName), TableName); + info.AddValue(nameof(ColumnName), ColumnName); + info.AddValue(nameof(DataTypeName), DataTypeName); + info.AddValue(nameof(ConstraintName), ConstraintName); + info.AddValue(nameof(File), File); + info.AddValue(nameof(Line), Line); + info.AddValue(nameof(Routine), Routine); + } + + /// + public override string ToString() + { + var builder = new StringBuilder(base.ToString()) + .AppendLine().Append(" Exception data:"); + + AppendLine(nameof(Severity), Severity); + AppendLine(nameof(SqlState), SqlState); + AppendLine(nameof(MessageText), MessageText); + AppendLine(nameof(Detail), Detail); + AppendLine(nameof(Hint), Hint); + AppendLine(nameof(Position), Position); + AppendLine(nameof(InternalPosition), InternalPosition); + AppendLine(nameof(InternalQuery), InternalQuery); + AppendLine(nameof(Where), Where); + AppendLine(nameof(SchemaName), SchemaName); + AppendLine(nameof(TableName), TableName); + AppendLine(nameof(ColumnName), ColumnName); + AppendLine(nameof(DataTypeName), DataTypeName); + AppendLine(nameof(ConstraintName), ConstraintName); + AppendLine(nameof(File), File); + AppendLine(nameof(Line), Line); + AppendLine(nameof(Routine), Routine); + + return builder.ToString(); + + void AppendLine(string propertyName, T propertyValue) + { + if (!EqualityComparer.Default.Equals(propertyValue, default!)) + builder.AppendLine().Append(" ").Append(propertyName).Append(": ").Append(propertyValue); + } + } + + /// + /// Specifies whether the exception is considered transient, that is, whether retrying the operation could + /// succeed (e.g. a network error). Check . + /// + public override bool IsTransient + { + get + { + switch (SqlState) + { + case PostgresErrorCodes.InsufficientResources: + case PostgresErrorCodes.DiskFull: + case PostgresErrorCodes.OutOfMemory: + case PostgresErrorCodes.TooManyConnections: + case PostgresErrorCodes.ConfigurationLimitExceeded: + case PostgresErrorCodes.CannotConnectNow: + case PostgresErrorCodes.SystemError: + case PostgresErrorCodes.IoError: + case PostgresErrorCodes.SerializationFailure: + case PostgresErrorCodes.DeadlockDetected: + case PostgresErrorCodes.LockNotAvailable: + case PostgresErrorCodes.ObjectInUse: + case PostgresErrorCodes.ObjectNotInPrerequisiteState: + case PostgresErrorCodes.ConnectionException: + case PostgresErrorCodes.ConnectionDoesNotExist: + case PostgresErrorCodes.ConnectionFailure: + case PostgresErrorCodes.SqlClientUnableToEstablishSqlConnection: + case PostgresErrorCodes.SqlServerRejectedEstablishmentOfSqlConnection: + case PostgresErrorCodes.TransactionResolutionUnknown: + return true; + default: + return false; + } + } + } + + #region Message Fields + + /// + /// Severity of the error or notice. + /// Always present. + /// + public string Severity { get; } + + /// + /// Severity of the error or notice, not localized. + /// Always present since PostgreSQL 9.6. + /// + public string InvariantSeverity { get; } + + /// + /// The SQLSTATE code for the error. + /// + /// + /// Always present. + /// Constants are defined in . + /// See https://www.postgresql.org/docs/current/static/errcodes-appendix.html + /// +#if NET5_0_OR_GREATER + public override string SqlState { get; } +#else + public string SqlState { get; } +#endif + + /// + /// The SQLSTATE code for the error. + /// + /// + /// Always present. + /// Constants are defined in . + /// See https://www.postgresql.org/docs/current/static/errcodes-appendix.html + /// + [Obsolete("Use SqlState instead")] + public string Code => SqlState; + + /// + /// The primary human-readable error message. This should be accurate but terse. + /// + /// + /// Always present. + /// + public string MessageText { get; } + + /// + /// An optional secondary error message carrying more detail about the problem. + /// May run to multiple lines. + /// + public string? Detail { get; } + + /// + /// An optional suggestion what to do about the problem. + /// This is intended to differ from Detail in that it offers advice (potentially inappropriate) rather than hard facts. + /// May run to multiple lines. + /// + public string? Hint { get; } + + /// + /// The field value is a decimal ASCII integer, indicating an error cursor position as an index into the original query string. + /// The first character has index 1, and positions are measured in characters not bytes. + /// 0 means not provided. + /// + public int Position { get; } + + /// + /// This is defined the same as the field, but it is used when the cursor position refers to an internally generated command rather than the one submitted by the client. + /// The field will always appear when this field appears. + /// 0 means not provided. + /// + public int InternalPosition { get; } + + /// + /// The text of a failed internally-generated command. + /// This could be, for example, a SQL query issued by a PL/pgSQL function. + /// + public string? InternalQuery { get; } + + /// + /// An indication of the context in which the error occurred. + /// Presently this includes a call stack traceback of active PL functions. + /// The trace is one entry per line, most recent first. + /// + public string? Where { get; } + + /// + /// If the error was associated with a specific database object, the name of the schema containing that object, if any. + /// + /// PostgreSQL 9.3 and up. + public string? SchemaName { get; } + + /// + /// Table name: if the error was associated with a specific table, the name of the table. + /// (Refer to the schema name field for the name of the table's schema.) + /// + /// PostgreSQL 9.3 and up. + public string? TableName { get; } + + /// + /// If the error was associated with a specific table column, the name of the column. + /// (Refer to the schema and table name fields to identify the table.) + /// + /// PostgreSQL 9.3 and up. + public string? ColumnName { get; } + + /// + /// If the error was associated with a specific data type, the name of the data type. + /// (Refer to the schema name field for the name of the data type's schema.) + /// + /// PostgreSQL 9.3 and up. + public string? DataTypeName { get; } + + /// + /// If the error was associated with a specific constraint, the name of the constraint. + /// Refer to fields listed above for the associated table or domain. + /// (For this purpose, indexes are treated as constraints, even if they weren't created with constraint syntax.) + /// + /// PostgreSQL 9.3 and up. + public string? ConstraintName { get; } + + /// + /// The file name of the source-code location where the error was reported. + /// + /// PostgreSQL 9.3 and up. + public string? File { get; } + + /// + /// The line number of the source-code location where the error was reported. + /// + public string? Line { get; } + + /// + /// The name of the source-code routine reporting the error. + /// + public string? Routine { get; } + + #endregion +} \ No newline at end of file diff --git a/LibExternal/Npgsql/PostgresMinimalDatabaseInfo.cs b/LibExternal/Npgsql/PostgresMinimalDatabaseInfo.cs new file mode 100644 index 0000000..64759a8 --- /dev/null +++ b/LibExternal/Npgsql/PostgresMinimalDatabaseInfo.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Npgsql.Internal; +using Npgsql.PostgresTypes; +using Npgsql.Util; +using NpgsqlTypes; + +namespace Npgsql; + +class PostgresMinimalDatabaseInfoFactory : INpgsqlDatabaseInfoFactory +{ + public Task Load(NpgsqlConnector conn, NpgsqlTimeout timeout, bool async) + => Task.FromResult( + conn.Settings.ServerCompatibilityMode == ServerCompatibilityMode.NoTypeLoading + ? (NpgsqlDatabaseInfo)new PostgresMinimalDatabaseInfo(conn) + : null + ); +} + +class PostgresMinimalDatabaseInfo : PostgresDatabaseInfo +{ + static readonly PostgresBaseType[] Types = typeof(NpgsqlDbType).GetFields() + .Select(f => f.GetCustomAttribute()) + .OfType() + .Select(a => new PostgresBaseType("pg_catalog", a.Name, a.OID)) + .ToArray(); + + protected override IEnumerable GetTypes() => Types; + + internal PostgresMinimalDatabaseInfo(NpgsqlConnector conn) + : base(conn) + { + HasIntegerDateTimes = !conn.PostgresParameters.TryGetValue("integer_datetimes", out var intDateTimes) || + intDateTimes == "on"; + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/PostgresNotice.cs b/LibExternal/Npgsql/PostgresNotice.cs new file mode 100644 index 0000000..a68d48a --- /dev/null +++ b/LibExternal/Npgsql/PostgresNotice.cs @@ -0,0 +1,213 @@ +using System; +using Npgsql.BackendMessages; +using Npgsql.Internal; + +namespace Npgsql; + +/// +/// PostgreSQL notices are non-critical messages generated by PostgreSQL, either as a result of a user query +/// (e.g. as a warning or informational notice), or due to outside activity (e.g. if the database administrator +/// initiates a "fast" database shutdown). +/// +/// +/// https://www.postgresql.org/docs/current/static/protocol-flow.html#PROTOCOL-ASYNC +/// +public sealed class PostgresNotice +{ + #region Message Fields + + /// + /// Severity of the error or notice. + /// Always present. + /// + public string Severity { get; set; } + + /// + /// Severity of the error or notice, not localized. + /// Always present since PostgreSQL 9.6. + /// + public string InvariantSeverity { get; } + + /// + /// The SQLSTATE code for the error. + /// + /// + /// Always present. + /// See https://www.postgresql.org/docs/current/static/errcodes-appendix.html + /// + public string SqlState { get; set; } + + /// + /// The SQLSTATE code for the error. + /// + /// + /// Always present. + /// See https://www.postgresql.org/docs/current/static/errcodes-appendix.html + /// + [Obsolete("Use SqlState instead")] + public string Code => SqlState; + + /// + /// The primary human-readable error message. This should be accurate but terse. + /// + /// + /// Always present. + /// + public string MessageText { get; set; } + + /// + /// An optional secondary error message carrying more detail about the problem. + /// May run to multiple lines. + /// + public string? Detail { get; set; } + + /// + /// An optional suggestion what to do about the problem. + /// This is intended to differ from Detail in that it offers advice (potentially inappropriate) rather than hard facts. + /// May run to multiple lines. + /// + public string? Hint { get; set; } + + /// + /// The field value is a decimal ASCII integer, indicating an error cursor position as an index into the original query string. + /// The first character has index 1, and positions are measured in characters not bytes. + /// 0 means not provided. + /// + public int Position { get; set; } + + /// + /// This is defined the same as the field, but it is used when the cursor position refers to an internally generated command rather than the one submitted by the client. + /// The field will always appear when this field appears. + /// 0 means not provided. + /// + public int InternalPosition { get; set; } + + /// + /// The text of a failed internally-generated command. + /// This could be, for example, a SQL query issued by a PL/pgSQL function. + /// + public string? InternalQuery { get; set; } + + /// + /// An indication of the context in which the error occurred. + /// Presently this includes a call stack traceback of active PL functions. + /// The trace is one entry per line, most recent first. + /// + public string? Where { get; set; } + + /// + /// If the error was associated with a specific database object, the name of the schema containing that object, if any. + /// + /// PostgreSQL 9.3 and up. + public string? SchemaName { get; set; } + + /// + /// Table name: if the error was associated with a specific table, the name of the table. + /// (Refer to the schema name field for the name of the table's schema.) + /// + /// PostgreSQL 9.3 and up. + public string? TableName { get; set; } + + /// + /// If the error was associated with a specific table column, the name of the column. + /// (Refer to the schema and table name fields to identify the table.) + /// + /// PostgreSQL 9.3 and up. + public string? ColumnName { get; set; } + + /// + /// If the error was associated with a specific data type, the name of the data type. + /// (Refer to the schema name field for the name of the data type's schema.) + /// + /// PostgreSQL 9.3 and up. + public string? DataTypeName { get; set; } + + /// + /// If the error was associated with a specific constraint, the name of the constraint. + /// Refer to fields listed above for the associated table or domain. + /// (For this purpose, indexes are treated as constraints, even if they weren't created with constraint syntax.) + /// + /// PostgreSQL 9.3 and up. + public string? ConstraintName { get; set; } + + /// + /// The file name of the source-code location where the error was reported. + /// + /// PostgreSQL 9.3 and up. + public string? File { get; set; } + + /// + /// The line number of the source-code location where the error was reported. + /// + public string? Line { get; set; } + + /// + /// The name of the source-code routine reporting the error. + /// + public string? Routine { get; set; } + + #endregion + + /// + /// Creates a new instance. + /// + public PostgresNotice(string severity, string invariantSeverity, string sqlState, string messageText) + : this(messageText, severity, invariantSeverity, sqlState, detail: null) {} + + /// + /// Creates a new instance. + /// + public PostgresNotice( + string messageText, string severity, string invariantSeverity, string sqlState, + string? detail = null, string? hint = null, int position = 0, int internalPosition = 0, + string? internalQuery = null, string? where = null, string? schemaName = null, string? tableName = null, + string? columnName = null, string? dataTypeName = null, string? constraintName = null, string? file = null, + string? line = null, string? routine = null) + { + MessageText = messageText; + Severity = severity; + InvariantSeverity = invariantSeverity; + SqlState = sqlState; + + Detail = detail; + Hint = hint; + Position = position; + InternalPosition = internalPosition; + InternalQuery = internalQuery; + Where = where; + SchemaName = schemaName; + TableName = tableName; + ColumnName = columnName; + DataTypeName = dataTypeName; + ConstraintName = constraintName; + File = file; + Line = line; + Routine = routine; + } + + PostgresNotice(ErrorOrNoticeMessage msg) + : this( + msg.Message, msg.Severity, msg.InvariantSeverity, msg.SqlState, + msg.Detail, msg.Hint, msg.Position, msg.InternalPosition, msg.InternalQuery, + msg.Where, msg.SchemaName, msg.TableName, msg.ColumnName, msg.DataTypeName, + msg.ConstraintName, msg.File, msg.Line, msg.Routine) {} + + internal static PostgresNotice Load(NpgsqlReadBuffer buf, bool includeDetail) + => new(ErrorOrNoticeMessage.Load(buf, includeDetail)); +} + +/// +/// Provides data for a PostgreSQL notice event. +/// +public sealed class NpgsqlNoticeEventArgs : EventArgs +{ + /// + /// The Notice that was sent from the database. + /// + public PostgresNotice Notice { get; } + + internal NpgsqlNoticeEventArgs(PostgresNotice notice) + { + Notice = notice; + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/PostgresTypes/PostgresArrayType.cs b/LibExternal/Npgsql/PostgresTypes/PostgresArrayType.cs new file mode 100644 index 0000000..cfeb89c --- /dev/null +++ b/LibExternal/Npgsql/PostgresTypes/PostgresArrayType.cs @@ -0,0 +1,37 @@ +using System.Diagnostics; + +namespace Npgsql.PostgresTypes; + +/// +/// Represents a PostgreSQL array data type, which can hold several multiple values in a single column. +/// +/// +/// See https://www.postgresql.org/docs/current/static/arrays.html. +/// +public class PostgresArrayType : PostgresType +{ + /// + /// The PostgreSQL data type of the element contained within this array. + /// + public PostgresType Element { get; } + + /// + /// Constructs a representation of a PostgreSQL array data type. + /// + protected internal PostgresArrayType(string ns, string internalName, uint oid, PostgresType elementPostgresType) + : base(ns, elementPostgresType.Name + "[]", internalName, oid) + { + Debug.Assert(internalName == '_' + elementPostgresType.InternalName); + Element = elementPostgresType; + Element.Array = this; + } + + // PostgreSQL array types have an underscore-prefixed name (_text), but we + // want to return the public text[] instead + /// + internal override string GetPartialNameWithFacets(int typeModifier) + => Element.GetPartialNameWithFacets(typeModifier) + "[]"; + + internal override PostgresFacets GetFacets(int typeModifier) + => Element.GetFacets(typeModifier); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/PostgresTypes/PostgresBaseType.cs b/LibExternal/Npgsql/PostgresTypes/PostgresBaseType.cs new file mode 100644 index 0000000..de9a7bc --- /dev/null +++ b/LibExternal/Npgsql/PostgresTypes/PostgresBaseType.cs @@ -0,0 +1,94 @@ + +namespace Npgsql.PostgresTypes; + +/// +/// Represents a PostgreSQL base data type, which is a simple scalar value. +/// +public class PostgresBaseType : PostgresType +{ + /// + protected internal PostgresBaseType(string ns, string internalName, uint oid) + : base(ns, TranslateInternalName(internalName), internalName, oid) + {} + + /// + internal override string GetPartialNameWithFacets(int typeModifier) + { + var facets = GetFacets(typeModifier); + if (facets == PostgresFacets.None) + return Name; + + return Name switch + { + // Special case for time, timestamp, timestamptz and timetz where the facet is embedded in the middle + "timestamp without time zone" => $"timestamp{facets} without time zone", + "time without time zone" => $"time{facets} without time zone", + "timestamp with time zone" => $"timestamp{facets} with time zone", + "time with time zone" => $"time{facets} with time zone", + + // We normalize character(1) to character - they mean the same + "character" when facets.Size == 1 => "character", + + _ => $"{Name}{facets}" + }; + } + + internal override PostgresFacets GetFacets(int typeModifier) + { + if (typeModifier == -1) + return PostgresFacets.None; + + switch (Name) + { + case "character": + return new PostgresFacets(typeModifier - 4, null, null); + case "character varying": + return new PostgresFacets(typeModifier - 4, null, null); // Max length + case "numeric": + case "decimal": + // See https://stackoverflow.com/questions/3350148/where-are-numeric-precision-and-scale-for-a-field-found-in-the-pg-catalog-tables + var precision = ((typeModifier - 4) >> 16) & 65535; + var scale = (typeModifier - 4) & 65535; + return new PostgresFacets(null, precision, scale == 0 ? (int?)null : scale); + case "timestamp without time zone": + case "time without time zone": + case "interval": + precision = typeModifier & 0xFFFF; + return new PostgresFacets(null, precision, null); + case "timestamp with time zone": + precision = typeModifier & 0xFFFF; + return new PostgresFacets(null, precision, null); + case "time with time zone": + precision = typeModifier & 0xFFFF; + return new PostgresFacets(null, precision, null); + case "bit": + case "bit varying": + return new PostgresFacets(typeModifier, null, null); + default: + return PostgresFacets.None; + } + } + + // The type names returned by PostgreSQL are internal names (int4 instead of + // integer). We perform translation to the user-facing standard names. + // https://www.postgresql.org/docs/current/static/datatype.html#DATATYPE-TABLE + static string TranslateInternalName(string internalName) + => internalName switch + { + "bool" => "boolean", + "bpchar" => "character", + "decimal" => "numeric", + "float4" => "real", + "float8" => "double precision", + "int2" => "smallint", + "int4" => "integer", + "int8" => "bigint", + "time" => "time without time zone", + "timestamp" => "timestamp without time zone", + "timetz" => "time with time zone", + "timestamptz" => "timestamp with time zone", + "varbit" => "bit varying", + "varchar" => "character varying", + _ => internalName + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/PostgresTypes/PostgresCompositeType.cs b/LibExternal/Npgsql/PostgresTypes/PostgresCompositeType.cs new file mode 100644 index 0000000..fb31254 --- /dev/null +++ b/LibExternal/Npgsql/PostgresTypes/PostgresCompositeType.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; + +namespace Npgsql.PostgresTypes; + +/// +/// Represents a PostgreSQL composite data type, which can hold multiple fields of varying types in a single column. +/// +/// +/// See https://www.postgresql.org/docs/current/static/rowtypes.html. +/// +public class PostgresCompositeType : PostgresType +{ + /// + /// Holds the name and types for all fields. + /// + public IReadOnlyList Fields => MutableFields; + + internal List MutableFields { get; } = new(); + + /// + /// Constructs a representation of a PostgreSQL array data type. + /// +#pragma warning disable CA2222 // Do not decrease inherited member visibility + internal PostgresCompositeType(string ns, string name, uint oid) + : base(ns, name, oid) {} +#pragma warning restore CA2222 // Do not decrease inherited member visibility + + /// + /// Represents a field in a PostgreSQL composite data type. + /// + public class Field + { + internal Field(string name, PostgresType type) + { + Name = name; + Type = type; + } + + /// + /// The name of the composite field. + /// + public string Name { get; } + /// + /// The type of the composite field. + /// + public PostgresType Type { get; } + + /// + public override string ToString() => $"{Name} => {Type}"; + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/PostgresTypes/PostgresDomainType.cs b/LibExternal/Npgsql/PostgresTypes/PostgresDomainType.cs new file mode 100644 index 0000000..f9b504b --- /dev/null +++ b/LibExternal/Npgsql/PostgresTypes/PostgresDomainType.cs @@ -0,0 +1,38 @@ +namespace Npgsql.PostgresTypes; + +/// +/// Represents a PostgreSQL domain type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/sql-createdomain.html. +/// +/// When PostgreSQL returns a RowDescription for a domain type, the type OID is the base type's +/// (so fetching a domain type over text returns a RowDescription for text). +/// However, when a composite type is returned, the type OID there is that of the domain, +/// so we provide "clean" support for domain types. +/// +public class PostgresDomainType : PostgresType +{ + /// + /// The PostgreSQL data type of the base type, i.e. the type this domain is based on. + /// + public PostgresType BaseType { get; } + + /// + /// True if the domain has a NOT NULL constraint, otherwise false. + /// + public bool NotNull { get; } + + /// + /// Constructs a representation of a PostgreSQL domain data type. + /// + protected internal PostgresDomainType(string ns, string name, uint oid, PostgresType baseType, bool notNull) + : base(ns, name, oid) + { + BaseType = baseType; + NotNull = notNull; + } + + internal override PostgresFacets GetFacets(int typeModifier) + => BaseType.GetFacets(typeModifier); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/PostgresTypes/PostgresEnumType.cs b/LibExternal/Npgsql/PostgresTypes/PostgresEnumType.cs new file mode 100644 index 0000000..f456946 --- /dev/null +++ b/LibExternal/Npgsql/PostgresTypes/PostgresEnumType.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace Npgsql.PostgresTypes; + +/// +/// Represents a PostgreSQL enum data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/datatype-enum.html. +/// +public class PostgresEnumType : PostgresType +{ + /// + /// The enum's fields. + /// + public IReadOnlyList Labels => MutableLabels; + + internal List MutableLabels { get; } = new(); + + /// + /// Constructs a representation of a PostgreSQL enum data type. + /// + protected internal PostgresEnumType(string ns, string name, uint oid) + : base(ns, name, oid) + {} +} \ No newline at end of file diff --git a/LibExternal/Npgsql/PostgresTypes/PostgresFacets.cs b/LibExternal/Npgsql/PostgresTypes/PostgresFacets.cs new file mode 100644 index 0000000..4c88724 --- /dev/null +++ b/LibExternal/Npgsql/PostgresTypes/PostgresFacets.cs @@ -0,0 +1,70 @@ +using System; +using System.Text; + +namespace Npgsql.PostgresTypes; + +readonly struct PostgresFacets : IEquatable +{ + internal static readonly PostgresFacets None = new(null, null, null); + + internal PostgresFacets(int? size, int? precision, int? scale) + { + Size = size; + Precision = precision; + Scale = scale; + } + + public readonly int? Size; + public readonly int? Precision; + public readonly int? Scale; + + public override bool Equals(object? o) + => o is PostgresFacets otherFacets && Equals(otherFacets); + + public bool Equals(PostgresFacets o) + => Size == o.Size && Precision == o.Precision && Scale == o.Scale; + + public static bool operator ==(PostgresFacets x, PostgresFacets y) => x.Equals(y); + + public static bool operator !=(PostgresFacets x, PostgresFacets y) => !(x == y); + + public override int GetHashCode() + { + var hashcode = Size?.GetHashCode() ?? 0; + hashcode = (hashcode * 397) ^ (Precision?.GetHashCode() ?? 0); + hashcode = (hashcode * 397) ^ (Scale?.GetHashCode() ?? 0); + return hashcode; + } + + public override string ToString() + { + if (Size == null && Precision == null && Scale == null) + return string.Empty; + + var sb = new StringBuilder().Append('('); + var needComma = false; + + if (Size != null) + { + sb.Append(Size); + needComma = true; + } + + if (Precision != null) + { + if (needComma) + sb.Append(", "); + sb.Append(Precision); + needComma = true; + } + + if (Scale != null) + { + if (needComma) + sb.Append(", "); + sb.Append(Scale); + } + + return sb.Append(')').ToString(); + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/PostgresTypes/PostgresMultirangeType.cs b/LibExternal/Npgsql/PostgresTypes/PostgresMultirangeType.cs new file mode 100644 index 0000000..3d35783 --- /dev/null +++ b/LibExternal/Npgsql/PostgresTypes/PostgresMultirangeType.cs @@ -0,0 +1,26 @@ +namespace Npgsql.PostgresTypes; + +/// +/// Represents a PostgreSQL multirange data type. +/// +/// +///

See https://www.postgresql.org/docs/current/static/rangetypes.html.

+///

Multirange types were introduced in PostgreSQL 14.

+///
+public class PostgresMultirangeType : PostgresType +{ + /// + /// The PostgreSQL data type of the range of this multirange. + /// + public PostgresRangeType Subrange { get; } + + /// + /// Constructs a representation of a PostgreSQL range data type. + /// + protected internal PostgresMultirangeType(string ns, string name, uint oid, PostgresRangeType rangePostgresType) + : base(ns, name, oid) + { + Subrange = rangePostgresType; + Subrange.Multirange = this; + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/PostgresTypes/PostgresRangeType.cs b/LibExternal/Npgsql/PostgresTypes/PostgresRangeType.cs new file mode 100644 index 0000000..bc981bd --- /dev/null +++ b/LibExternal/Npgsql/PostgresTypes/PostgresRangeType.cs @@ -0,0 +1,31 @@ +namespace Npgsql.PostgresTypes; + +/// +/// Represents a PostgreSQL range data type. +/// +/// +/// See https://www.postgresql.org/docs/current/static/rangetypes.html. +/// +public class PostgresRangeType : PostgresType +{ + /// + /// The PostgreSQL data type of the subtype of this range. + /// + public PostgresType Subtype { get; } + + /// + /// The PostgreSQL data type of the multirange of this range. + /// + public PostgresMultirangeType? Multirange { get; internal set; } + + /// + /// Constructs a representation of a PostgreSQL range data type. + /// + protected internal PostgresRangeType( + string ns, string name, uint oid, PostgresType subtypePostgresType) + : base(ns, name, oid) + { + Subtype = subtypePostgresType; + Subtype.Range = this; + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/PostgresTypes/PostgresType.cs b/LibExternal/Npgsql/PostgresTypes/PostgresType.cs new file mode 100644 index 0000000..7ea878b --- /dev/null +++ b/LibExternal/Npgsql/PostgresTypes/PostgresType.cs @@ -0,0 +1,114 @@ +using System; +using System.Linq; + +namespace Npgsql.PostgresTypes; + +/// +/// Represents a PostgreSQL data type, such as int4 or text, as discovered from pg_type. +/// This class is abstract, see derived classes for concrete types of PostgreSQL types. +/// +/// +/// Instances of this class are shared between connections to the same databases. +/// For more info about what this class and its subclasses represent, see +/// https://www.postgresql.org/docs/current/static/catalog-pg-type.html. +/// +public abstract class PostgresType +{ + #region Constructors + + /// + /// Constructs a representation of a PostgreSQL data type. + /// + /// The data type's namespace (or schema). + /// The data type's name. + /// The data type's OID. + protected PostgresType(string ns, string name, uint oid) + : this(ns, name, name, oid) {} + + /// + /// Constructs a representation of a PostgreSQL data type. + /// + /// The data type's namespace (or schema). + /// The data type's name. + /// The data type's internal name (e.g. _int4 for integer[]). + /// The data type's OID. + protected PostgresType(string ns, string name, string internalName, uint oid) + { + Namespace = ns; + Name = name; + FullName = Namespace + '.' + Name; + InternalName = internalName; + OID = oid; + } + + #endregion + + #region Public Properties + + /// + /// The data type's OID - a unique id identifying the data type in a given database (in pg_type). + /// + public uint OID { get; } + + /// + /// The data type's namespace (or schema). + /// + public string Namespace { get; } + + /// + /// The data type's name. + /// + /// + /// Note that this is the standard, user-displayable type name (e.g. integer[]) rather than the internal + /// PostgreSQL name as it is in pg_type (_int4). See for the latter. + /// + public string Name { get; } + + /// + /// The full name of the backend type, including its namespace. + /// + public string FullName { get; } + + /// + /// A display name for this backend type, including the namespace unless it is pg_catalog (the namespace + /// for all built-in types). + /// + public string DisplayName => Namespace == "pg_catalog" ? Name : FullName; + + /// + /// The data type's internal PostgreSQL name (e.g. integer[] not _int4). + /// See for a more user-friendly name. + /// + public string InternalName { get; } + + /// + /// If a PostgreSQL array type exists for this type, it will be referenced here. + /// Otherwise null. + /// + public PostgresArrayType? Array { get; internal set; } + + /// + /// If a PostgreSQL range type exists for this type, it will be referenced here. + /// Otherwise null. + /// + public PostgresRangeType? Range { get; internal set; } + + #endregion + + internal virtual string GetPartialNameWithFacets(int typeModifier) => Name; + + /// + /// Generates the type name including any facts (size, precision, scale), given the PostgreSQL type modifier. + /// + internal string GetDisplayNameWithFacets(int typeModifier) + => Namespace == "pg_catalog" + ? GetPartialNameWithFacets(typeModifier) + : Namespace + '.' + GetPartialNameWithFacets(typeModifier); + + internal virtual PostgresFacets GetFacets(int typeModifier) => PostgresFacets.None; + + /// + /// Returns a string that represents the current object. + /// + public override string ToString() => DisplayName; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/PostgresTypes/PostgresUnknownType.cs b/LibExternal/Npgsql/PostgresTypes/PostgresUnknownType.cs new file mode 100644 index 0000000..a520df9 --- /dev/null +++ b/LibExternal/Npgsql/PostgresTypes/PostgresUnknownType.cs @@ -0,0 +1,16 @@ +namespace Npgsql.PostgresTypes; + +/// +/// Represents a PostgreSQL data type that isn't known to Npgsql and cannot be handled. +/// +public class UnknownBackendType : PostgresType +{ + internal static readonly PostgresType Instance = new UnknownBackendType(); + + /// + /// Constructs a the unknown backend type. + /// +#pragma warning disable CA2222 // Do not decrease inherited member visibility + UnknownBackendType() : base("", "", 0) { } +#pragma warning restore CA2222 // Do not decrease inherited member visibility +} \ No newline at end of file diff --git a/LibExternal/Npgsql/PregeneratedMessages.cs b/LibExternal/Npgsql/PregeneratedMessages.cs new file mode 100644 index 0000000..3d315ff --- /dev/null +++ b/LibExternal/Npgsql/PregeneratedMessages.cs @@ -0,0 +1,55 @@ +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using Npgsql.Internal; + +namespace Npgsql; + +static class PregeneratedMessages +{ + static PregeneratedMessages() + { +#pragma warning disable CS8625 + // This is the only use of a write buffer without a connector, for in-memory construction of + // pregenerated messages. + using var buf = new NpgsqlWriteBuffer(null, new MemoryStream(), null, NpgsqlWriteBuffer.MinimumSize, Encoding.ASCII); +#pragma warning restore CS8625 + + BeginTransRepeatableRead = Generate(buf, "BEGIN ISOLATION LEVEL REPEATABLE READ"); + BeginTransSerializable = Generate(buf, "BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE"); + BeginTransReadCommitted = Generate(buf, "BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED"); + BeginTransReadUncommitted = Generate(buf, "BEGIN TRANSACTION ISOLATION LEVEL READ UNCOMMITTED"); + CommitTransaction = Generate(buf, "COMMIT"); + RollbackTransaction = Generate(buf, "ROLLBACK"); + DiscardAll = Generate(buf, "DISCARD ALL"); + } + + internal static byte[] Generate(NpgsqlWriteBuffer buf, string query) + { + Debug.Assert(query.All(c => c < 128)); + + var queryByteLen = Encoding.ASCII.GetByteCount(query); + + buf.WriteByte(FrontendMessageCode.Query); + buf.WriteInt32(4 + // Message length (including self excluding code) + queryByteLen + // Query byte length + 1); // Null terminator + + buf.WriteString(query, queryByteLen, false).Wait(); + buf.WriteByte(0); + + var bytes = buf.GetContents(); + buf.Clear(); + return bytes; + } + + internal static readonly byte[] BeginTransRepeatableRead; + internal static readonly byte[] BeginTransSerializable; + internal static readonly byte[] BeginTransReadCommitted; + internal static readonly byte[] BeginTransReadUncommitted; + internal static readonly byte[] CommitTransaction; + internal static readonly byte[] RollbackTransaction; + + internal static readonly byte[] DiscardAll; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/PreparedStatement.cs b/LibExternal/Npgsql/PreparedStatement.cs new file mode 100644 index 0000000..b7ef026 --- /dev/null +++ b/LibExternal/Npgsql/PreparedStatement.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Npgsql.BackendMessages; + +namespace Npgsql; + +/// +/// Internally represents a statement has been prepared, is in the process of being prepared, or is a +/// candidate for preparation (i.e. awaiting further usages). +/// +[DebuggerDisplay("{Name} ({State}): {Sql}")] +class PreparedStatement +{ + readonly PreparedStatementManager _manager; + + internal string Sql { get; } + + internal string? Name; + + internal RowDescriptionMessage? Description; + + internal int Usages; + + internal PreparedState State { get; set; } + + internal bool IsPrepared => State == PreparedState.Prepared; + + /// + /// If true, the user explicitly requested this statement be prepared. It does not get closed as part of + /// the automatic preparation LRU mechanism. + /// + internal bool IsExplicit { get; } + + /// + /// If this statement is about to be prepared, but replaces a previous statement which needs to be closed, + /// this holds the name of the previous statement. Otherwise null. + /// + internal PreparedStatement? StatementBeingReplaced; + + internal int AutoPreparedSlotIndex { get; set; } + + internal DateTime LastUsed { get; set; } + + /// + /// Contains the handler types for a prepared statement's parameters, for overloaded cases (same SQL, different param types) + /// Only populated after the statement has been prepared (i.e. null for candidates). + /// + internal Type[]? HandlerParamTypes { get; private set; } + + static readonly Type[] EmptyParamTypes = Type.EmptyTypes; + + internal static PreparedStatement CreateExplicit( + PreparedStatementManager manager, + string sql, + string name, + List parameters, + PreparedStatement? statementBeingReplaced) + { + var pStatement = new PreparedStatement(manager, sql, true) + { + Name = name, + StatementBeingReplaced = statementBeingReplaced + }; + pStatement.SetParamTypes(parameters); + return pStatement; + } + + internal static PreparedStatement CreateAutoPrepareCandidate(PreparedStatementManager manager, string sql) + => new(manager, sql, false); + + PreparedStatement(PreparedStatementManager manager, string sql, bool isExplicit) + { + _manager = manager; + Sql = sql; + IsExplicit = isExplicit; + State = PreparedState.NotPrepared; + } + + internal void SetParamTypes(List parameters) + { + Debug.Assert(HandlerParamTypes == null); + if (parameters.Count == 0) + { + HandlerParamTypes = EmptyParamTypes; + return; + } + + HandlerParamTypes = new Type[parameters.Count]; + for (var i = 0; i < parameters.Count; i++) + HandlerParamTypes[i] = parameters[i].Handler!.GetType(); + } + + internal bool DoParametersMatch(List parameters) + { + if (HandlerParamTypes!.Length != parameters.Count) + return false; + + for (var i = 0; i < HandlerParamTypes.Length; i++) + if (HandlerParamTypes[i] != parameters[i].Handler!.GetType()) + return false; + + return true; + } + + internal void AbortPrepare() + { + Debug.Assert(State == PreparedState.BeingPrepared); + + // We were planned for preparation, but a failure occurred and we did not carry that out. + // Remove it from the BySql dictionary, and place back the statement we were planned to replace (possibly null), setting + // its state back to prepared. + _manager.BySql.Remove(Sql); + + if (!IsExplicit) + { + _manager.AutoPrepared[AutoPreparedSlotIndex] = StatementBeingReplaced; + if (StatementBeingReplaced is not null) + StatementBeingReplaced.State = PreparedState.Prepared; + } + + State = PreparedState.Unprepared; + } + + internal void CompleteUnprepare() + { + _manager.BySql.Remove(Sql); + _manager.NumPrepared--; + + State = PreparedState.Unprepared; + } + + public override string ToString() => Sql; +} + +/// +/// The state of a . +/// +enum PreparedState +{ + /// + /// The statement hasn't been prepared yet, nor is it in the process of being prepared. + /// This is the value for autoprepare candidates which haven't been prepared yet, and is also + /// a temporary state during preparation. + /// + NotPrepared, + + /// + /// The statement is in the process of being prepared. + /// + BeingPrepared, + + /// + /// The statement has been fully prepared and can be executed. + /// + Prepared, + + /// + /// The statement is in the process of being unprepared. This is a temporary state that only occurs during + /// unprepare. Specifically, it means that a Close message for the statement has already been written + /// to the write buffer. + /// + BeingUnprepared, + + /// + /// The statement has been unprepared and is no longer usable. + /// + Unprepared +} \ No newline at end of file diff --git a/LibExternal/Npgsql/PreparedStatementManager.cs b/LibExternal/Npgsql/PreparedStatementManager.cs new file mode 100644 index 0000000..d0591d3 --- /dev/null +++ b/LibExternal/Npgsql/PreparedStatementManager.cs @@ -0,0 +1,264 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Npgsql.Internal; +using Npgsql.Logging; + +namespace Npgsql; + +class PreparedStatementManager +{ + internal int MaxAutoPrepared { get; } + internal int UsagesBeforePrepare { get; } + + internal Dictionary BySql { get; } = new(); + internal PreparedStatement?[] AutoPrepared { get; } + + readonly PreparedStatement?[] _candidates; + + /// + /// Total number of current prepared statements (whether explicit or automatic). + /// + internal int NumPrepared; + + readonly NpgsqlConnector _connector; + + internal string NextPreparedStatementName() => "_p" + (++_preparedStatementIndex); + ulong _preparedStatementIndex; + + static readonly NpgsqlLogger Log = NpgsqlLogManager.CreateLogger(nameof(PreparedStatementManager)); + + internal const int CandidateCount = 100; + + internal PreparedStatementManager(NpgsqlConnector connector) + { + _connector = connector; + MaxAutoPrepared = connector.Settings.MaxAutoPrepare; + UsagesBeforePrepare = connector.Settings.AutoPrepareMinUsages; + if (MaxAutoPrepared > 0) + { + if (MaxAutoPrepared > 256) + Log.Warn($"{nameof(MaxAutoPrepared)} is over 256, performance degradation may occur. Please report via an issue.", connector.Id); + AutoPrepared = new PreparedStatement[MaxAutoPrepared]; + _candidates = new PreparedStatement[CandidateCount]; + } + else + { + AutoPrepared = null!; + _candidates = null!; + } + } + + internal PreparedStatement? GetOrAddExplicit(NpgsqlBatchCommand batchCommand) + { + var sql = batchCommand.FinalCommandText!; + + PreparedStatement? statementBeingReplaced = null; + if (BySql.TryGetValue(sql, out var pStatement)) + { + Debug.Assert(pStatement.State != PreparedState.Unprepared); + if (pStatement.IsExplicit) + { + // Great, we've found an explicit prepared statement. + // We just need to check that the parameter types correspond, since prepared statements are + // only keyed by SQL (to prevent pointless allocations). If we have a mismatch, simply run unprepared. + return pStatement.DoParametersMatch(batchCommand.PositionalParameters!) + ? pStatement + : null; + } + + // We've found an autoprepare statement (candidate or otherwise) + switch (pStatement.State) + { + case PreparedState.NotPrepared: + // Found a candidate for autopreparation. Remove it and prepare explicitly. + RemoveCandidate(pStatement); + break; + case PreparedState.Prepared: + // The statement has already been autoprepared. We need to "promote" it to explicit. + statementBeingReplaced = pStatement; + break; + case PreparedState.Unprepared: + throw new InvalidOperationException($"Found unprepared statement in {nameof(PreparedStatementManager)}"); + default: + throw new ArgumentOutOfRangeException(); + } + } + + // Statement hasn't been prepared yet + return BySql[sql] = PreparedStatement.CreateExplicit(this, sql, NextPreparedStatementName(), batchCommand.PositionalParameters, statementBeingReplaced); + } + + internal PreparedStatement? TryGetAutoPrepared(NpgsqlBatchCommand batchCommand) + { + var sql = batchCommand.FinalCommandText!; + if (!BySql.TryGetValue(sql, out var pStatement)) + { + // New candidate. Find an empty candidate slot or eject a least-used one. + int slotIndex = -1, leastUsages = int.MaxValue; + var lastUsed = DateTime.MaxValue; + for (var i = 0; i < _candidates.Length; i++) + { + var candidate = _candidates[i]; + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + // ReSharper disable HeuristicUnreachableCode + if (candidate == null) // Found an unused candidate slot, return immediately + { + slotIndex = i; + break; + } + // ReSharper restore HeuristicUnreachableCode + if (candidate.Usages < leastUsages) + { + leastUsages = candidate.Usages; + slotIndex = i; + lastUsed = candidate.LastUsed; + } + else if (candidate.Usages == leastUsages && candidate.LastUsed < lastUsed) + { + slotIndex = i; + lastUsed = candidate.LastUsed; + } + } + + var leastUsed = _candidates[slotIndex]; + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + if (leastUsed != null) + BySql.Remove(leastUsed.Sql); + pStatement = BySql[sql] = _candidates[slotIndex] = PreparedStatement.CreateAutoPrepareCandidate(this, sql); + } + + switch (pStatement.State) + { + case PreparedState.NotPrepared: + break; + + case PreparedState.Prepared: + case PreparedState.BeingPrepared: + // The statement has already been prepared (explicitly or automatically), or has been selected + // for preparation (earlier identical statement in the same command). + // We just need to check that the parameter types correspond, since prepared statements are + // only keyed by SQL (to prevent pointless allocations). If we have a mismatch, simply run unprepared. + if (!pStatement.DoParametersMatch(batchCommand.PositionalParameters)) + return null; + // Prevent this statement from being replaced within this batch + pStatement.LastUsed = DateTime.MaxValue; + return pStatement; + + case PreparedState.BeingUnprepared: + // The statement is being replaced by an earlier statement in this same batch. + return null; + + default: + Debug.Fail($"Unexpected {nameof(PreparedState)} in auto-preparation: {pStatement.State}"); + break; + } + + if (++pStatement.Usages < UsagesBeforePrepare) + { + // Statement still hasn't passed the usage threshold, no automatic preparation. + // Return null for unprepared execution. + pStatement.LastUsed = DateTime.UtcNow; + return null; + } + + // Bingo, we've just passed the usage threshold, statement should get prepared + Log.Trace($"Automatically preparing statement: {sql}", _connector.Id); + + // Look for either an empty autoprepare slot, or the least recently used prepared statement which we'll replace it. + var oldestTimestamp = DateTime.MaxValue; + var selectedIndex = -1; + for (var i = 0; i < AutoPrepared.Length; i++) + { + var slot = AutoPrepared[i]; + + if (slot is null) + { + // We found a free slot, exit the loop immediately + selectedIndex = i; + break; + } + + switch (slot.State) + { + case PreparedState.Prepared: + if (slot.LastUsed < oldestTimestamp) + { + selectedIndex = i; + oldestTimestamp = slot.LastUsed; + } + break; + + case PreparedState.BeingPrepared: + // Slot has already been selected for preparation by an earlier statement in this batch. Skip it. + continue; + + default: + throw new Exception( + $"Invalid {nameof(PreparedState)} state {slot.State} encountered when scanning prepared statement slots"); + } + } + + if (selectedIndex == -1) + { + // We're here if we couldn't find a free slot or a prepared statement to replace - this means all slots are taken by + // statements being prepared in this batch. + return null; + } + + var oldPreparedStatement = AutoPrepared[selectedIndex]; + + if (oldPreparedStatement is null) + { + pStatement.Name = "_auto" + selectedIndex; + } + else + { + pStatement.Name = oldPreparedStatement.Name; + pStatement.StatementBeingReplaced = oldPreparedStatement; + oldPreparedStatement.State = PreparedState.BeingUnprepared; + } + + pStatement.AutoPreparedSlotIndex = selectedIndex; + AutoPrepared[selectedIndex] = pStatement; + + RemoveCandidate(pStatement); + + // Make sure this statement isn't replaced by a later statement in the same batch. + pStatement.LastUsed = DateTime.MaxValue; + + // Note that the parameter types are only set at the moment of preparation - in the candidate phase + // there's no differentiation between overloaded statements, which are a pretty rare case, saving + // allocations. + pStatement.SetParamTypes(batchCommand.PositionalParameters); + + return pStatement; + } + + void RemoveCandidate(PreparedStatement candidate) + { + var i = 0; + for (; i < _candidates.Length; i++) + { + if (_candidates[i] == candidate) + { + _candidates[i] = null; + return; + } + } + Debug.Assert(i < _candidates.Length); + } + + internal void ClearAll() + { + BySql.Clear(); + NumPrepared = 0; + _preparedStatementIndex = 0; + if (AutoPrepared is not null) + for (var i = 0; i < AutoPrepared.Length; i++) + AutoPrepared[i] = null; + if (_candidates != null) + for (var i = 0; i < _candidates.Length; i++) + _candidates[i] = null; + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Replication/PgOutput/Messages/PgOutputReplicationMessage.cs b/LibExternal/Npgsql/Replication/PgOutput/Messages/PgOutputReplicationMessage.cs new file mode 100644 index 0000000..b93e27f --- /dev/null +++ b/LibExternal/Npgsql/Replication/PgOutput/Messages/PgOutputReplicationMessage.cs @@ -0,0 +1,17 @@ +using NpgsqlTypes; +using System; + +namespace Npgsql.Replication.PgOutput.Messages; + +/// +/// The base class of all Logical Replication Protocol Messages +/// +/// +/// See https://www.postgresql.org/docs/current/protocol-logicalrep-message-formats.html for details about the +/// protocol. +/// +public abstract class PgOutputReplicationMessage : ReplicationMessage +{ + /// + public override string ToString() => GetType().Name; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Replication/PgOutput/Messages/RelationMessage.cs b/LibExternal/Npgsql/Replication/PgOutput/Messages/RelationMessage.cs new file mode 100644 index 0000000..f9be4a1 --- /dev/null +++ b/LibExternal/Npgsql/Replication/PgOutput/Messages/RelationMessage.cs @@ -0,0 +1,139 @@ +using NpgsqlTypes; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using Npgsql.BackendMessages; + +namespace Npgsql.Replication.PgOutput.Messages; + +/// +/// Logical Replication Protocol relation message +/// +public sealed class RelationMessage : TransactionalMessage +{ + /// + /// ID of the relation. + /// + public uint RelationId { get; private set; } + + /// + /// Namespace (empty string for pg_catalog). + /// + public string Namespace { get; private set; } = string.Empty; + + /// + /// Relation name. + /// + public string RelationName { get; private set; } = string.Empty; + + /// + /// Replica identity setting for the relation (same as relreplident in pg_class): + /// columns used to form “replica identity” for rows. + /// + public ReplicaIdentitySetting ReplicaIdentity { get; private set; } + + /// + /// Relation columns + /// + public IReadOnlyList Columns => InternalColumns; + + internal ReadOnlyArrayBuffer InternalColumns { get; } = new(); + + internal RowDescriptionMessage RowDescription { get; set; } = null!; + + internal RelationMessage() {} + + internal RelationMessage Populate( + NpgsqlLogSequenceNumber walStart, NpgsqlLogSequenceNumber walEnd, DateTime serverClock, uint? transactionXid, uint relationId, string ns, + string relationName, ReplicaIdentitySetting relationReplicaIdentitySetting) + { + base.Populate(walStart, walEnd, serverClock, transactionXid); + + RelationId = relationId; + Namespace = ns; + RelationName = relationName; + ReplicaIdentity = relationReplicaIdentitySetting; + + return this; + } + + /// + /// Represents a column in a Logical Replication Protocol relation message + /// + public readonly struct Column + { + internal Column(ColumnFlags flags, string columnName, uint dataTypeId, int typeModifier) + { + Flags = flags; + ColumnName = columnName; + DataTypeId = dataTypeId; + TypeModifier = typeModifier; + } + + /// + /// Flags for the column. + /// + public ColumnFlags Flags { get; } + + /// + /// Name of the column. + /// + public string ColumnName { get; } + + /// + /// ID of the column's data type. + /// + public uint DataTypeId { get; } + + /// + /// Type modifier of the column (atttypmod). + /// + public int TypeModifier { get; } + + /// + /// Flags for the column. + /// + [Flags] + public enum ColumnFlags + { + /// + /// No flags. + /// + None = 0, + + /// + /// Marks the column as part of the key. + /// + PartOfKey = 1 + } + } + + /// + /// Replica identity setting for the relation (same as relreplident in pg_class). + /// + /// + /// See + /// + public enum ReplicaIdentitySetting : byte + { + /// + /// Default (primary key, if any). + /// + Default = (byte)'d', + + /// + /// Nothing. + /// + Nothing = (byte)'n', + + /// + /// All columns. + /// + AllColumns = (byte)'f', + + /// + /// Index with indisreplident set (same as nothing if the index used has been dropped) + /// + IndexWithIndIsReplIdent = (byte)'i' + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Replication/PgOutput/Messages/TransactionalMessage.cs b/LibExternal/Npgsql/Replication/PgOutput/Messages/TransactionalMessage.cs new file mode 100644 index 0000000..d5aac68 --- /dev/null +++ b/LibExternal/Npgsql/Replication/PgOutput/Messages/TransactionalMessage.cs @@ -0,0 +1,22 @@ +using System; +using NpgsqlTypes; + +namespace Npgsql.Replication.PgOutput.Messages; + +/// +/// The common base class for all streaming replication messages that can be part of a streaming transaction (protocol V2) +/// +public abstract class TransactionalMessage : PgOutputReplicationMessage +{ + /// + /// Xid of the transaction (only present for streamed transactions). + /// + public uint? TransactionXid { get; private set; } + + private protected void Populate(NpgsqlLogSequenceNumber walStart, NpgsqlLogSequenceNumber walEnd, DateTime serverClock, uint? transactionXid) + { + base.Populate(walStart, walEnd, serverClock); + + TransactionXid = transactionXid; + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Replication/PgOutput/ReadonlyArrayBuffer.cs b/LibExternal/Npgsql/Replication/PgOutput/ReadonlyArrayBuffer.cs new file mode 100644 index 0000000..5a7d15f --- /dev/null +++ b/LibExternal/Npgsql/Replication/PgOutput/ReadonlyArrayBuffer.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Npgsql.Replication.PgOutput.Messages; + +namespace Npgsql.Replication.PgOutput; + +class ReadOnlyArrayBuffer : IReadOnlyList +{ + public static readonly ReadOnlyArrayBuffer Empty = new(); + T[] _items; + int _size; + + public ReadOnlyArrayBuffer() + => _items = Array.Empty(); + + ReadOnlyArrayBuffer(T[] items) + { + _items = items; + _size = items.Length; + } + + public IEnumerator GetEnumerator() + { + for (var i = 0; i < _size; i++) + { + yield return _items[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public int Count + { + get => _size; + internal set + { + if (_items.Length < value) + _items = new T[value]; + + _size = value; + } + } + + public T this[int index] + { + get => index < _size ? _items[index] : throw new IndexOutOfRangeException(); + internal set => _items[index] = value; + } + + public ReadOnlyArrayBuffer Clone() + { + var newItems = new T[_size]; + if (_size > 0) + Array.Copy(_items, newItems, _size); + return new(newItems); + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Replication/ReplicationMessage.cs b/LibExternal/Npgsql/Replication/ReplicationMessage.cs new file mode 100644 index 0000000..50b1371 --- /dev/null +++ b/LibExternal/Npgsql/Replication/ReplicationMessage.cs @@ -0,0 +1,32 @@ +using NpgsqlTypes; +using System; + +namespace Npgsql.Replication; + +/// +/// The common base class for all streaming replication messages +/// +public abstract class ReplicationMessage +{ + /// + /// The starting point of the WAL data in this message. + /// + public NpgsqlLogSequenceNumber WalStart { get; private set; } + + /// + /// The current end of WAL on the server. + /// + public NpgsqlLogSequenceNumber WalEnd { get; private set; } + + /// + /// The server's system clock at the time this message was transmitted, as microseconds since midnight on 2000-01-01. + /// + public DateTime ServerClock { get; private set; } + + private protected void Populate(NpgsqlLogSequenceNumber walStart, NpgsqlLogSequenceNumber walEnd, DateTime serverClock) + { + WalStart = walStart; + WalEnd = walEnd; + ServerClock = serverClock; + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Schema/DbColumnSchemaGenerator.cs b/LibExternal/Npgsql/Schema/DbColumnSchemaGenerator.cs new file mode 100644 index 0000000..8045617 --- /dev/null +++ b/LibExternal/Npgsql/Schema/DbColumnSchemaGenerator.cs @@ -0,0 +1,276 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Data; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Transactions; +using Npgsql.BackendMessages; +using Npgsql.Internal; +using Npgsql.Internal.TypeHandlers; +using Npgsql.Internal.TypeHandlers.CompositeHandlers; +using Npgsql.Util; + +namespace Npgsql.Schema; + +class DbColumnSchemaGenerator +{ + readonly RowDescriptionMessage _rowDescription; + readonly NpgsqlConnection _connection; + readonly bool _fetchAdditionalInfo; + + internal DbColumnSchemaGenerator(NpgsqlConnection connection, RowDescriptionMessage rowDescription, bool fetchAdditionalInfo) + { + _connection = connection; + _rowDescription = rowDescription; + _fetchAdditionalInfo = fetchAdditionalInfo; + } + + #region Columns queries + + static string GenerateColumnsQuery(Version pgVersion, string columnFieldFilter) => + $@"SELECT + typ.oid AS typoid, nspname, relname, attname, attrelid, attnum, attnotnull, + {(pgVersion.IsGreaterOrEqual(10) ? "attidentity != ''" : "FALSE")} AS isidentity, + CASE WHEN typ.typtype = 'd' THEN typ.typtypmod ELSE atttypmod END AS typmod, + CASE WHEN atthasdef THEN (SELECT pg_get_expr(adbin, cls.oid) FROM pg_attrdef WHERE adrelid = cls.oid AND adnum = attr.attnum) ELSE NULL END AS default, + CASE WHEN col.is_updatable = 'YES' THEN true ELSE false END AS is_updatable, + EXISTS ( + SELECT * FROM pg_index + WHERE pg_index.indrelid = cls.oid AND + pg_index.indisprimary AND + attnum = ANY (indkey) + ) AS isprimarykey, + EXISTS ( + SELECT * FROM pg_index + WHERE pg_index.indrelid = cls.oid AND + pg_index.indisunique AND + pg_index.{(pgVersion.IsGreaterOrEqual(11) ? "indnkeyatts" : "indnatts")} = 1 AND + attnum = pg_index.indkey[0] + ) AS isunique +FROM pg_attribute AS attr +JOIN pg_type AS typ ON attr.atttypid = typ.oid +JOIN pg_class AS cls ON cls.oid = attr.attrelid +JOIN pg_namespace AS ns ON ns.oid = cls.relnamespace +LEFT OUTER JOIN information_schema.columns AS col ON col.table_schema = nspname AND + col.table_name = relname AND + col.column_name = attname +WHERE + atttypid <> 0 AND + relkind IN ('r', 'v', 'm') AND + NOT attisdropped AND + nspname NOT IN ('pg_catalog', 'information_schema') AND + attnum > 0 AND + ({columnFieldFilter}) +ORDER BY attnum"; + + /// + /// Stripped-down version of , mainly to support Amazon Redshift. + /// + static string GenerateOldColumnsQuery(string columnFieldFilter) => + $@"SELECT + typ.oid AS typoid, nspname, relname, attname, attrelid, attnum, attnotnull, + CASE WHEN typ.typtype = 'd' THEN typ.typtypmod ELSE atttypmod END AS typmod, + CASE WHEN atthasdef THEN (SELECT pg_get_expr(adbin, cls.oid) FROM pg_attrdef WHERE adrelid = cls.oid AND adnum = attr.attnum) ELSE NULL END AS default, + TRUE AS is_updatable, /* Supported only since PG 8.2 */ + FALSE AS isprimarykey, /* Can't do ANY() on pg_index.indkey which is int2vector */ + FALSE AS isunique /* Can't do ANY() on pg_index.indkey which is int2vector */ +FROM pg_attribute AS attr +JOIN pg_type AS typ ON attr.atttypid = typ.oid +JOIN pg_class AS cls ON cls.oid = attr.attrelid +JOIN pg_namespace AS ns ON ns.oid = cls.relnamespace +LEFT OUTER JOIN information_schema.columns AS col ON col.table_schema = nspname AND + col.table_name = relname AND + col.column_name = attname +WHERE + atttypid <> 0 AND + relkind IN ('r', 'v', 'm') AND + NOT attisdropped AND + nspname NOT IN ('pg_catalog', 'information_schema') AND + attnum > 0 AND + ({columnFieldFilter}) +ORDER BY attnum"; + + #endregion Column queries + + internal async Task> GetColumnSchema(bool async, CancellationToken cancellationToken = default) + { + // This is mainly for Amazon Redshift + var oldQueryMode = _connection.PostgreSqlVersion < new Version(8, 2); + + var numFields = _rowDescription.Count; + var result = new List(numFields); + for (var i = 0; i < numFields; i++) + result.Add(null); + var populatedColumns = 0; + + if (_fetchAdditionalInfo) + { + // We have two types of fields - those which correspond to actual database columns + // and those that don't (e.g. SELECT 8). For the former we load lots of info from + // the backend (if fetchAdditionalInfo is true), for the latter we only have the RowDescription + + var columnFieldFilter = _rowDescription + .Where(f => f.TableOID != 0) // Only column fields + .Select(c => $"(attr.attrelid={c.TableOID} AND attr.attnum={c.ColumnAttributeNumber})") + .Join(" OR "); + + if (columnFieldFilter != string.Empty) + { + var query = oldQueryMode + ? GenerateOldColumnsQuery(columnFieldFilter) + : GenerateColumnsQuery(_connection.PostgreSqlVersion, columnFieldFilter); + + using var scope = new TransactionScope( + TransactionScopeOption.Suppress, + async ? TransactionScopeAsyncFlowOption.Enabled : TransactionScopeAsyncFlowOption.Suppress); + using var connection = (NpgsqlConnection)((ICloneable)_connection).Clone(); + + await connection.Open(async, cancellationToken); + + using var cmd = new NpgsqlCommand(query, connection); + var reader = await cmd.ExecuteReader(CommandBehavior.Default, async, cancellationToken); + try + { + while (async ? await reader.ReadAsync(cancellationToken) : reader.Read()) + { + var column = LoadColumnDefinition(reader, _connection.Connector!.TypeMapper.DatabaseInfo, oldQueryMode); + for (var ordinal = 0; ordinal < numFields; ordinal++) + { + var field = _rowDescription[ordinal]; + if (field.TableOID == column.TableOID && + field.ColumnAttributeNumber == column.ColumnAttributeNumber) + { + populatedColumns++; + + if (column.ColumnOrdinal.HasValue) + column = column.Clone(); + + // The column's ordinal is with respect to the resultset, not its table + column.ColumnOrdinal = ordinal; + result[ordinal] = column; + } + } + } + } + finally + { + if (async) + await reader.DisposeAsync(); + else + reader.Dispose(); + } + } + } + + // We had some fields which don't correspond to regular table columns (or fetchAdditionalInfo is false). + // Fill in whatever info we have from the RowDescription itself + for (var i = 0; i < numFields; i++) + { + var column = result[i]; + var field = _rowDescription[i]; + + if (column is null) + { + column = SetUpNonColumnField(field); + column.ColumnOrdinal = i; + result[i] = column; + populatedColumns++; + } + + column.ColumnName = field.Name; + column.IsAliased = column.BaseColumnName is null ? default(bool?) : (column.BaseColumnName != column.ColumnName); + } + + if (populatedColumns != numFields) + throw new NpgsqlException("Could not load all columns for the resultset"); + + return result.AsReadOnly()!; + } + + NpgsqlDbColumn LoadColumnDefinition(NpgsqlDataReader reader, NpgsqlDatabaseInfo databaseInfo, bool oldQueryMode) + { + // We don't set ColumnName here. It should always contain the column alias rather than + // the table column name (i.e. in case of "SELECT foo AS foo_alias"). It will be set later. + var column = new NpgsqlDbColumn + { + AllowDBNull = !reader.GetBoolean(reader.GetOrdinal("attnotnull")), + BaseCatalogName = _connection.Database!, + BaseSchemaName = reader.GetString(reader.GetOrdinal("nspname")), + BaseServerName = _connection.Host!, + BaseTableName = reader.GetString(reader.GetOrdinal("relname")), + BaseColumnName = reader.GetString(reader.GetOrdinal("attname")), + ColumnAttributeNumber = reader.GetInt16(reader.GetOrdinal("attnum")), + IsKey = reader.GetBoolean(reader.GetOrdinal("isprimarykey")), + IsReadOnly = !reader.GetBoolean(reader.GetOrdinal("is_updatable")), + IsUnique = reader.GetBoolean(reader.GetOrdinal("isunique")), + + TableOID = reader.GetFieldValue(reader.GetOrdinal("attrelid")), + TypeOID = reader.GetFieldValue(reader.GetOrdinal("typoid")) + }; + + column.PostgresType = databaseInfo.ByOID[column.TypeOID]; + column.DataTypeName = column.PostgresType.DisplayName; // Facets do not get included + + var defaultValueOrdinal = reader.GetOrdinal("default"); + column.DefaultValue = reader.IsDBNull(defaultValueOrdinal) ? null : reader.GetString(defaultValueOrdinal); + + column.IsAutoIncrement = + !oldQueryMode && reader.GetBoolean(reader.GetOrdinal("isidentity")) || + column.DefaultValue != null && column.DefaultValue.StartsWith("nextval(", StringComparison.Ordinal); + + ColumnPostConfig(column, reader.GetInt32(reader.GetOrdinal("typmod"))); + + return column; + } + + NpgsqlDbColumn SetUpNonColumnField(FieldDescription field) + { + // ColumnName and BaseColumnName will be set later + var column = new NpgsqlDbColumn + { + BaseCatalogName = _connection.Database!, + BaseServerName = _connection.Host!, + IsReadOnly = true, + DataTypeName = field.PostgresType.DisplayName, + TypeOID = field.TypeOID, + TableOID = field.TableOID, + ColumnAttributeNumber = field.ColumnAttributeNumber, + PostgresType = field.PostgresType + }; + + ColumnPostConfig(column, field.TypeModifier); + + return column; + } + + /// + /// Performs some post-setup configuration that's common to both table columns and non-columns. + /// + void ColumnPostConfig(NpgsqlDbColumn column, int typeModifier) + { + var typeMapper = _connection.Connector!.TypeMapper; + + column.NpgsqlDbType = typeMapper.GetTypeInfoByOid(column.TypeOID).npgsqlDbType; + column.DataType = typeMapper.TryResolveByOID(column.TypeOID, out var handler) + ? handler.GetFieldType() + : null; + + if (column.DataType != null) + { + column.IsLong = handler is ByteaHandler; + + if (handler is ICompositeHandler) + column.UdtAssemblyQualifiedName = column.DataType.AssemblyQualifiedName; + } + + var facets = column.PostgresType.GetFacets(typeModifier); + if (facets.Size != null) + column.ColumnSize = facets.Size; + if (facets.Precision != null) + column.NumericPrecision = facets.Precision; + if (facets.Scale != null) + column.NumericScale = facets.Scale; + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Schema/NpgsqlDbColumn.cs b/LibExternal/Npgsql/Schema/NpgsqlDbColumn.cs new file mode 100644 index 0000000..cfd8b3f --- /dev/null +++ b/LibExternal/Npgsql/Schema/NpgsqlDbColumn.cs @@ -0,0 +1,228 @@ +using System; +using System.Data.Common; +using System.Runtime.CompilerServices; +using Npgsql.PostgresTypes; +using NpgsqlTypes; + +namespace Npgsql.Schema; + +/// +/// Provides schema information about a column. +/// +/// +/// Note that this can correspond to a field returned in a query which isn't an actual table column +/// +/// See https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldatareader.getschematable(v=vs.110).aspx +/// for information on the meaning of the different fields. +/// +public class NpgsqlDbColumn : DbColumn +{ + /// + /// Initializes a new instance of the class. + /// + public NpgsqlDbColumn() + { + PostgresType = UnknownBackendType.Instance; + + // Not supported in PostgreSQL + IsExpression = false; + IsAliased = false; + IsHidden = false; + IsIdentity = false; + } + + internal NpgsqlDbColumn Clone() => + Unsafe.As(MemberwiseClone()); + + #region Standard fields + // ReSharper disable once InconsistentNaming + /// + public new bool? AllowDBNull + { + get => base.AllowDBNull; + protected internal set => base.AllowDBNull = value; + } + + /// + public new string BaseCatalogName + { + get => base.BaseCatalogName!; + protected internal set => base.BaseCatalogName = value; + } + + /// + public new string? BaseColumnName + { + get => base.BaseColumnName; + protected internal set => base.BaseColumnName = value; + } + + /// + public new string? BaseSchemaName + { + get => base.BaseSchemaName; + protected internal set => base.BaseSchemaName = value; + } + + /// + public new string BaseServerName + { + get => base.BaseServerName!; + protected internal set => base.BaseServerName = value; + } + + /// + public new string? BaseTableName + { + get => base.BaseTableName; + protected internal set => base.BaseTableName = value; + } + + /// + public new string ColumnName + { + get => base.ColumnName; + protected internal set => base.ColumnName = value; + } + + /// + public new int? ColumnOrdinal + { + get => base.ColumnOrdinal; + protected internal set => base.ColumnOrdinal = value; + } + + /// + public new int? ColumnSize + { + get => base.ColumnSize; + protected internal set => base.ColumnSize = value; + } + + /// + public new bool? IsAliased + { + get => base.IsAliased; + protected internal set => base.IsAliased = value; + } + + /// + public new bool? IsAutoIncrement + { + get => base.IsAutoIncrement; + protected internal set => base.IsAutoIncrement = value; + } + + /// + public new bool? IsKey + { + get => base.IsKey; + protected internal set => base.IsKey = value; + } + + /// + public new bool? IsLong + { + get => base.IsLong; + protected internal set => base.IsLong = value; + } + + /// + public new bool? IsReadOnly + { + get => base.IsReadOnly; + protected internal set => base.IsReadOnly = value; + } + + /// + public new bool? IsUnique + { + get => base.IsUnique; + protected internal set => base.IsUnique = value; + } + + /// + public new int? NumericPrecision + { + get => base.NumericPrecision; + protected internal set => base.NumericPrecision = value; + } + + /// + public new int? NumericScale + { + get => base.NumericScale; + protected internal set => base.NumericScale = value; + } + + /// + public new string? UdtAssemblyQualifiedName + { + get => base.UdtAssemblyQualifiedName; + protected internal set => base.UdtAssemblyQualifiedName = value; + } + + /// + public new Type? DataType + { + get => base.DataType; + protected internal set => base.DataType = value; + } + + /// + public new string DataTypeName + { + get => base.DataTypeName!; + protected internal set => base.DataTypeName = value; + } + + #endregion Standard fields + + #region Npgsql-specific fields + + /// + /// The describing the type of this column. + /// + public PostgresType PostgresType { get; internal set; } + + /// + /// The OID of the type of this column in the PostgreSQL pg_type catalog table. + /// + public uint TypeOID { get; internal set; } + + /// + /// The OID of the PostgreSQL table of this column. + /// + public uint TableOID { get; internal set; } + + /// + /// The column's position within its table. Note that this is different from , + /// which is the column's position within the resultset. + /// + public short? ColumnAttributeNumber { get; internal set; } + + /// + /// The default SQL expression for this column. + /// + public string? DefaultValue { get; internal set; } + + /// + /// The value for this column's type. + /// + public NpgsqlDbType? NpgsqlDbType { get; internal set; } + + /// + public override object? this[string propertyName] + => propertyName switch + { + nameof(PostgresType) => PostgresType, + nameof(TypeOID) => TypeOID, + nameof(TableOID) => TableOID, + nameof(ColumnAttributeNumber) => ColumnAttributeNumber, + nameof(DefaultValue) => DefaultValue, + nameof(NpgsqlDbType) => NpgsqlDbType, + _ => base[propertyName] + }; + + #endregion Npgsql-specific fields +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Shims/ReadOnlySpanOfCharExtensions.cs b/LibExternal/Npgsql/Shims/ReadOnlySpanOfCharExtensions.cs new file mode 100644 index 0000000..c805e98 --- /dev/null +++ b/LibExternal/Npgsql/Shims/ReadOnlySpanOfCharExtensions.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Npgsql.Netstandard20; + +static class ReadOnlySpanOfCharExtensions +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ParseInt(this ReadOnlySpan span) + => int.Parse(span +#if NETSTANDARD2_0 + .ToString() +#endif + ); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/SingleThreadSynchronizationContext.cs b/LibExternal/Npgsql/SingleThreadSynchronizationContext.cs new file mode 100644 index 0000000..6b31947 --- /dev/null +++ b/LibExternal/Npgsql/SingleThreadSynchronizationContext.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Threading; +using Npgsql.Logging; + +namespace Npgsql; + +sealed class SingleThreadSynchronizationContext : SynchronizationContext, IDisposable +{ + readonly BlockingCollection _tasks = new(); + readonly object _lockObject = new(); + volatile Thread? _thread; + bool _doingWork; + + const int ThreadStayAliveMs = 10000; + readonly string _threadName; + + static readonly NpgsqlLogger Log = NpgsqlLogManager.CreateLogger(nameof(SingleThreadSynchronizationContext)); + + internal SingleThreadSynchronizationContext(string threadName) + => _threadName = threadName; + + internal Disposable Enter() => new(this); + + public override void Post(SendOrPostCallback callback, object? state) + { + _tasks.Add(new CallbackAndState { Callback = callback, State = state }); + + lock (_lockObject) + { + if (!_doingWork) + { + // Either there is no thread, or the current thread is exiting + // In which case, wait for it to complete + var currentThread = _thread; + currentThread?.Join(); + Debug.Assert(_thread is null); + _doingWork = true; + _thread = new Thread(WorkLoop) { Name = _threadName, IsBackground = true }; + _thread.Start(); + } + } + } + + public void Dispose() + { + _tasks.CompleteAdding(); + + var thread = _thread; + thread?.Join(); + + _tasks.Dispose(); + } + + void WorkLoop() + { + SetSynchronizationContext(this); + + try + { + while (true) + { + var taken = _tasks.TryTake(out var callbackAndState, ThreadStayAliveMs); + if (!taken) + { + lock (_lockObject) + { + if (_tasks.Count == 0) + { + _doingWork = false; + return; + } + } + + continue; + } + + try + { + Debug.Assert(_doingWork); + callbackAndState.Callback(callbackAndState.State); + } + catch (Exception e) + { + Log.Error($"Exception caught in {nameof(SingleThreadSynchronizationContext)}", e); + } + } + } + catch (Exception e) + { + // Here we attempt to catch any exception coming from BlockingCollection _tasks + Log.Error($"Exception caught in {nameof(SingleThreadSynchronizationContext)}", e); + lock (_lockObject) + _doingWork = false; + } + finally + { + Debug.Assert(!_doingWork); + _thread = null; + } + } + + struct CallbackAndState + { + internal SendOrPostCallback Callback; + internal object? State; + } + + internal readonly struct Disposable : IDisposable + { + readonly SynchronizationContext? _synchronizationContext; + + internal Disposable(SynchronizationContext synchronizationContext) + { + _synchronizationContext = Current; + SetSynchronizationContext(synchronizationContext); + } + + public void Dispose() + => SetSynchronizationContext(_synchronizationContext); + } +} diff --git a/LibExternal/Npgsql/SqlQueryParser.cs b/LibExternal/Npgsql/SqlQueryParser.cs new file mode 100644 index 0000000..58361c5 --- /dev/null +++ b/LibExternal/Npgsql/SqlQueryParser.cs @@ -0,0 +1,532 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace Npgsql; + +class SqlQueryParser +{ + readonly Dictionary _paramIndexMap = new(); + readonly StringBuilder _rewrittenSql = new(); + + /// + ///

+ /// Receives a user SQL query as passed in by the user in or + /// , and rewrites it for PostgreSQL compatibility. + ///

+ ///

+ /// This includes doing rewriting named parameter placeholders to positional (@p => $1), and splitting the query + /// up by semicolons (legacy batching, SELECT 1; SELECT 2). + ///

+ ///
+ /// The user-facing being executed. + /// Whether PostgreSQL standards-conforming are used. + /// + /// A bool indicating whether parameters contains a list of preconfigured parameters or an empty list to be filled with derived + /// parameters. + /// + internal void ParseRawQuery( + NpgsqlCommand? command, + bool standardConformingStrings = true, + bool deriveParameters = false) + => ParseRawQuery(command, batchCommand: null, standardConformingStrings, deriveParameters); + + /// + ///

+ /// Receives a user SQL query as passed in by the user in or + /// , and rewrites it for PostgreSQL compatibility. + ///

+ ///

+ /// This includes doing rewriting named parameter placeholders to positional (@p => $1), and splitting the query + /// up by semicolons (legacy batching, SELECT 1; SELECT 2). + ///

+ ///
+ /// The user-facing being executed. + /// Whether PostgreSQL standards-conforming are used. + /// + /// A bool indicating whether parameters contains a list of preconfigured parameters or an empty list to be filled with derived + /// parameters. + /// + internal void ParseRawQuery( + NpgsqlBatchCommand? batchCommand, + bool standardConformingStrings = true, + bool deriveParameters = false) + => ParseRawQuery(command: null, batchCommand, standardConformingStrings, deriveParameters); + + void ParseRawQuery( + NpgsqlCommand? command, + NpgsqlBatchCommand? batchCommand, + bool standardConformingStrings = true, + bool deriveParameters = false) + { + ReadOnlySpan sql; + NpgsqlParameterCollection parameters; + List? batchCommands; + + var statementIndex = 0; + if (command is null) + { + // Batching mode. We're processing only one batch - if we encounter a semicolon (legacy batching), that's an error. + Debug.Assert(batchCommand is not null); + sql = batchCommand.CommandText.AsSpan(); + parameters = batchCommand.Parameters; + batchCommands = null; + } + else + { + // Command mode. Semicolons (legacy batching) may occur. + Debug.Assert(batchCommand is null); + sql = command.CommandText.AsSpan(); + parameters = command.Parameters; + batchCommands = command.InternalBatchCommands; + MoveToNextBatchCommand(); + } + + Debug.Assert(batchCommand is not null); + Debug.Assert(parameters.PlaceholderType != PlaceholderType.Positional); + Debug.Assert(deriveParameters == false || parameters.Count == 0); + // Debug.Assert(batchCommand.PositionalParameters is not null && batchCommand.PositionalParameters.Count == 0); + + _paramIndexMap.Clear(); + _rewrittenSql.Clear(); + + var currCharOfs = 0; + var end = sql.Length; + var ch = '\0'; + int dollarTagStart; + int dollarTagEnd; + var currTokenBeg = 0; + var blockCommentLevel = 0; + var parenthesisLevel = 0; + + None: + if (currCharOfs >= end) + goto Finish; + var lastChar = ch; + ch = sql[currCharOfs++]; + NoneContinue: + for (; ; lastChar = ch, ch = sql[currCharOfs++]) + { + switch (ch) + { + case '/': + goto BlockCommentBegin; + case '-': + goto LineCommentBegin; + case '\'': + if (standardConformingStrings) + goto Quoted; + else + goto Escaped; + case '$': + if (!IsIdentifier(lastChar)) + goto DollarQuotedStart; + else + break; + case '"': + goto DoubleQuoted; + case ':': + if (lastChar != ':') + goto NamedParamStart; + else + break; + case '@': + if (lastChar != '@') + goto NamedParamStart; + else + break; + case ';': + if (parenthesisLevel == 0) + goto SemiColon; + break; + case '(': + parenthesisLevel++; + break; + case ')': + parenthesisLevel--; + break; + case 'e': + case 'E': + if (!IsLetter(lastChar)) + goto EscapedStart; + else + break; + } + + if (currCharOfs >= end) + goto Finish; + } + + NamedParamStart: + if (currCharOfs < end) + { + lastChar = ch; + ch = sql[currCharOfs]; + if (IsParamNameChar(ch)) + { + if (currCharOfs - 1 > currTokenBeg) + _rewrittenSql.Append(sql.Slice(currTokenBeg, currCharOfs - 1 - currTokenBeg)); + currTokenBeg = currCharOfs++ - 1; + goto NamedParam; + } + currCharOfs++; + goto NoneContinue; + } + goto Finish; + + NamedParam: + // We have already at least one character of the param name + for (;;) + { + lastChar = ch; + if (currCharOfs >= end || !IsParamNameChar(ch = sql[currCharOfs])) + { + var paramName = sql.Slice(currTokenBeg + 1, currCharOfs - (currTokenBeg + 1)).ToString(); + + if (!_paramIndexMap.TryGetValue(paramName, out var index)) + { + // Parameter hasn't been seen before in this query + if (!parameters.TryGetValue(paramName, out var parameter)) + { + if (deriveParameters) + { + parameter = new NpgsqlParameter { ParameterName = paramName }; + parameters.Add(parameter); + } + else + { + // Parameter placeholder does not match a parameter on this command. + // Leave the text as it was in the SQL, it may not be a an actual placeholder + _rewrittenSql.Append(sql.Slice(currTokenBeg, currCharOfs - currTokenBeg)); + currTokenBeg = currCharOfs; + if (currCharOfs >= end) + goto Finish; + + currCharOfs++; + goto NoneContinue; + } + } + + if (!parameter.IsInputDirection) + throw new Exception($"Parameter '{paramName}' referenced in SQL but is an out-only parameter"); + + batchCommand.PositionalParameters.Add(parameter); + index = _paramIndexMap[paramName] = batchCommand.PositionalParameters.Count; + } + _rewrittenSql.Append('$'); + _rewrittenSql.Append(index); + currTokenBeg = currCharOfs; + + if (currCharOfs >= end) + goto Finish; + + currCharOfs++; + goto NoneContinue; + } + + currCharOfs++; + } + + Quoted: + while (currCharOfs < end) + { + if (sql[currCharOfs++] == '\'') + { + ch = '\0'; + goto None; + } + } + goto Finish; + + DoubleQuoted: + while (currCharOfs < end) + { + if (sql[currCharOfs++] == '"') + { + ch = '\0'; + goto None; + } + } + goto Finish; + + EscapedStart: + if (currCharOfs < end) + { + lastChar = ch; + ch = sql[currCharOfs++]; + if (ch == '\'') + goto Escaped; + goto NoneContinue; + } + goto Finish; + + Escaped: + while (currCharOfs < end) + { + ch = sql[currCharOfs++]; + switch (ch) + { + case '\'': + goto MaybeConcatenatedEscaped; + case '\\': + { + if (currCharOfs >= end) + goto Finish; + currCharOfs++; + break; + } + } + } + goto Finish; + + MaybeConcatenatedEscaped: + while (currCharOfs < end) + { + ch = sql[currCharOfs++]; + switch (ch) + { + case '\r': + case '\n': + goto MaybeConcatenatedEscaped2; + case ' ': + case '\t': + case '\f': + continue; + default: + lastChar = '\0'; + goto NoneContinue; + } + } + goto Finish; + + MaybeConcatenatedEscaped2: + while (currCharOfs < end) + { + ch = sql[currCharOfs++]; + switch (ch) + { + case '\'': + goto Escaped; + case '-': + { + if (currCharOfs >= end) + goto Finish; + ch = sql[currCharOfs++]; + if (ch == '-') + goto MaybeConcatenatedEscapeAfterComment; + lastChar = '\0'; + goto NoneContinue; + } + case ' ': + case '\t': + case '\n': + case '\r': + case '\f': + continue; + default: + lastChar = '\0'; + goto NoneContinue; + } + } + goto Finish; + + MaybeConcatenatedEscapeAfterComment: + while (currCharOfs < end) + { + ch = sql[currCharOfs++]; + if (ch == '\r' || ch == '\n') + goto MaybeConcatenatedEscaped2; + } + goto Finish; + + DollarQuotedStart: + if (currCharOfs < end) + { + ch = sql[currCharOfs]; + if (ch == '$') + { + // Empty tag + dollarTagStart = dollarTagEnd = currCharOfs; + currCharOfs++; + goto DollarQuoted; + } + if (IsIdentifierStart(ch)) + { + dollarTagStart = currCharOfs; + currCharOfs++; + goto DollarQuotedInFirstDelim; + } + lastChar = '$'; + currCharOfs++; + goto NoneContinue; + } + goto Finish; + + DollarQuotedInFirstDelim: + while (currCharOfs < end) + { + lastChar = ch; + ch = sql[currCharOfs++]; + if (ch == '$') + { + dollarTagEnd = currCharOfs - 1; + goto DollarQuoted; + } + if (!IsDollarTagIdentifier(ch)) + goto NoneContinue; + } + goto Finish; + + DollarQuoted: + var tag = sql.Slice(dollarTagStart - 1, dollarTagEnd - dollarTagStart + 2); + var pos = sql.Slice(dollarTagEnd + 1).IndexOf(tag); + if (pos == -1) + { + currCharOfs = end; + goto Finish; + } + pos += dollarTagEnd + 1; // If the substring is found adjust the position to be relative to the entire span + currCharOfs = pos + dollarTagEnd - dollarTagStart + 2; + ch = '\0'; + goto None; + + LineCommentBegin: + if (currCharOfs < end) + { + ch = sql[currCharOfs++]; + if (ch == '-') + goto LineComment; + lastChar = '\0'; + goto NoneContinue; + } + goto Finish; + + LineComment: + while (currCharOfs < end) + { + ch = sql[currCharOfs++]; + if (ch == '\r' || ch == '\n') + goto None; + } + goto Finish; + + BlockCommentBegin: + while (currCharOfs < end) + { + ch = sql[currCharOfs++]; + if (ch == '*') + { + blockCommentLevel++; + goto BlockComment; + } + if (ch != '/') + { + if (blockCommentLevel > 0) + goto BlockComment; + lastChar = '\0'; + goto NoneContinue; + } + } + goto Finish; + + BlockComment: + while (currCharOfs < end) + { + ch = sql[currCharOfs++]; + switch (ch) + { + case '*': + goto BlockCommentEnd; + case '/': + goto BlockCommentBegin; + } + } + goto Finish; + + BlockCommentEnd: + while (currCharOfs < end) + { + ch = sql[currCharOfs++]; + if (ch == '/') + { + if (--blockCommentLevel > 0) + goto BlockComment; + goto None; + } + if (ch != '*') + goto BlockComment; + } + goto Finish; + + SemiColon: + _rewrittenSql.Append(sql.Slice(currTokenBeg, currCharOfs - currTokenBeg - 1)); + batchCommand.FinalCommandText = _rewrittenSql.ToString(); + while (currCharOfs < end) + { + ch = sql[currCharOfs]; + if (char.IsWhiteSpace(ch)) + { + currCharOfs++; + continue; + } + // TODO: Handle end of line comment? Although psql doesn't seem to handle them... + + // We've found a non-whitespace character after a semicolon - this is legacy batching. + + if (command is null) + { + throw new NotSupportedException( + $"Specifying multiple SQL statements in a single {nameof(NpgsqlBatchCommand)} isn't supported, " + + "please remove all semicolons."); + } + + statementIndex++; + MoveToNextBatchCommand(); + _paramIndexMap.Clear(); + _rewrittenSql.Clear(); + + currTokenBeg = currCharOfs; + goto None; + } + if (batchCommands is not null && batchCommands.Count > statementIndex + 1) + batchCommands.RemoveRange(statementIndex + 1, batchCommands.Count - (statementIndex + 1)); + return; + + Finish: + _rewrittenSql.Append(sql.Slice(currTokenBeg, end - currTokenBeg)); + batchCommand.FinalCommandText = _rewrittenSql.ToString(); + if (batchCommands is not null && batchCommands.Count > statementIndex + 1) + batchCommands.RemoveRange(statementIndex + 1, batchCommands.Count - (statementIndex + 1)); + + void MoveToNextBatchCommand() + { + Debug.Assert(batchCommands is not null); + if (batchCommands.Count > statementIndex) + { + batchCommand = batchCommands[statementIndex]; + batchCommand.Reset(); + } + else + { + batchCommand = new NpgsqlBatchCommand(); + batchCommands.Add(batchCommand); + } + } + } + + static bool IsLetter(char ch) + => 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z'; + + static bool IsIdentifierStart(char ch) + => 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || 128 <= ch && ch <= 255; + + static bool IsDollarTagIdentifier(char ch) + => 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || '0' <= ch && ch <= '9' || ch == '_' || 128 <= ch && ch <= 255; + + static bool IsIdentifier(char ch) + => 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || '0' <= ch && ch <= '9' || ch == '_' || ch == '$' || 128 <= ch && ch <= 255; + + static bool IsParamNameChar(char ch) + => char.IsLetterOrDigit(ch) || ch == '_' || ch == '.'; // why dot?? +} \ No newline at end of file diff --git a/LibExternal/Npgsql/TaskExtensions.cs b/LibExternal/Npgsql/TaskExtensions.cs new file mode 100644 index 0000000..6875311 --- /dev/null +++ b/LibExternal/Npgsql/TaskExtensions.cs @@ -0,0 +1,128 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Npgsql.Util; + +namespace Npgsql; + +static class TaskExtensions +{ + /// + /// Utility that simplifies awaiting a task with a timeout. If the given task does not + /// complete within , a is thrown. + /// + /// The task to be awaited + /// How much time to allow to complete before throwing a + /// An awaitable task that represents the original task plus the timeout + internal static async Task WithTimeout(this Task task, NpgsqlTimeout timeout) + { + if (!timeout.IsSet) + return await task; + var timeLeft = timeout.CheckAndGetTimeLeft(); + if (task != await Task.WhenAny(task, Task.Delay(timeLeft))) + throw new TimeoutException(); + return await task; + } + + /// + /// Allows you to cancel awaiting for a non-cancellable task. + /// + /// + /// Read https://blogs.msdn.com/b/pfxteam/archive/2012/10/05/how-do-i-cancel-non-cancelable-async-operations.aspx + /// and be very careful with this. + /// + internal static async Task WithCancellation(this Task task, CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); + using (cancellationToken.Register(s => ((TaskCompletionSource)s!).TrySetResult(true), tcs)) + if (task != await Task.WhenAny(task, tcs.Task)) + throw new TaskCanceledException(task); + return await task; + } + + internal static Task WithCancellationAndTimeout(this Task task, NpgsqlTimeout timeout, CancellationToken cancellationToken) + => task.WithCancellation(cancellationToken).WithTimeout(timeout); + +#if !NET5_0_OR_GREATER + /// + /// Utility that simplifies awaiting a task with a timeout. If the given task does not + /// complete within , a is thrown. + /// + /// The task to be awaited + /// How much time to allow to complete before throwing a + /// An awaitable task that represents the original task plus the timeout + internal static async Task WithTimeout(this Task task, NpgsqlTimeout timeout) + { + if (!timeout.IsSet) + { + await task; + return; + } + var timeLeft = timeout.CheckAndGetTimeLeft(); + if (task != await Task.WhenAny(task, Task.Delay(timeLeft))) + throw new TimeoutException(); + await task; + } + + /// + /// Allows you to cancel awaiting for a non-cancellable task. + /// + /// + /// Read https://blogs.msdn.com/b/pfxteam/archive/2012/10/05/how-do-i-cancel-non-cancelable-async-operations.aspx + /// and be very careful with this. + /// + internal static async Task WithCancellation(this Task task, CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); + using (cancellationToken.Register(s => ((TaskCompletionSource)s!).TrySetResult(true), tcs)) + if (task != await Task.WhenAny(task, tcs.Task)) + throw new TaskCanceledException(task); + await task; + } + + internal static Task WithCancellationAndTimeout(this Task task, NpgsqlTimeout timeout, CancellationToken cancellationToken) + => task.WithCancellation(cancellationToken).WithTimeout(timeout); +#endif + + internal static async Task ExecuteWithTimeout(Func func, NpgsqlTimeout timeout, CancellationToken cancellationToken) + { + CancellationTokenSource? combinedCts = null; + try + { + var combinedCancellationToken = GetCombinedCancellationToken(ref combinedCts, timeout, cancellationToken); + await func(combinedCancellationToken); + } + finally + { + combinedCts?.Dispose(); + } + } + + internal static async Task ExecuteWithTimeout(Func> func, NpgsqlTimeout timeout, CancellationToken cancellationToken) + { + CancellationTokenSource? combinedCts = null; + try + { + var combinedCancellationToken = GetCombinedCancellationToken(ref combinedCts, timeout, cancellationToken); + return await func(combinedCancellationToken); + } + finally + { + combinedCts?.Dispose(); + } + } + + static CancellationToken GetCombinedCancellationToken(ref CancellationTokenSource? combinedCts, NpgsqlTimeout timeout, CancellationToken cancellationToken) + { + var finalCt = cancellationToken; + + if (timeout.IsSet) + { + combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + combinedCts.CancelAfter((int)timeout.CheckAndGetTimeLeft().TotalMilliseconds); + finalCt = combinedCts.Token; + } + + return finalCt; + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/ThrowHelper.cs b/LibExternal/Npgsql/ThrowHelper.cs new file mode 100644 index 0000000..1a05ab5 --- /dev/null +++ b/LibExternal/Npgsql/ThrowHelper.cs @@ -0,0 +1,40 @@ +using Npgsql.BackendMessages; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using Npgsql.Internal.TypeHandling; + +namespace Npgsql; + +static class ThrowHelper +{ + [DoesNotReturn] + internal static void ThrowInvalidCastException_NotSupportedType(NpgsqlTypeHandler handler, NpgsqlParameter? parameter, Type type) + { + var parameterName = parameter is null + ? null + : parameter.TrimmedName == string.Empty + ? $"${parameter.Collection!.IndexOf(parameter) + 1}" + : parameter.TrimmedName; + + throw new InvalidCastException(parameterName is null + ? $"Cannot write a value of CLR type '{type}' as database type '{handler.PgDisplayName}'." + : $"Cannot write a value of CLR type '{type}' as database type '{handler.PgDisplayName}' for parameter '{parameterName}'."); + } + + [DoesNotReturn] + internal static void ThrowInvalidCastException_NoValue(FieldDescription field) => + throw new InvalidCastException($"Column '{field.Name}' is null."); + + [DoesNotReturn] + internal static void ThrowInvalidOperationException_NoPropertyGetter(Type type, MemberInfo property) => + throw new InvalidOperationException($"Composite type '{type}' cannot be written because the '{property}' property has no getter."); + + [DoesNotReturn] + internal static void ThrowInvalidOperationException_NoPropertySetter(Type type, MemberInfo property) => + throw new InvalidOperationException($"Composite type '{type}' cannot be read because the '{property}' property has no setter."); + + [DoesNotReturn] + internal static void ThrowInvalidOperationException_BinaryImportParametersMismatch(int columnCount, int valueCount) => + throw new InvalidOperationException($"The binary import operation was started with {columnCount} column(s), but {valueCount} value(s) were provided."); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/TypeMapping/BuiltInTypeHandlerResolver.cs b/LibExternal/Npgsql/TypeMapping/BuiltInTypeHandlerResolver.cs new file mode 100644 index 0000000..3da83f1 --- /dev/null +++ b/LibExternal/Npgsql/TypeMapping/BuiltInTypeHandlerResolver.cs @@ -0,0 +1,759 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.Specialized; +using System.Data; +using System.Net; +using System.Net.NetworkInformation; +using System.Numerics; +using System.Text.Json; +using Npgsql.Internal; +using Npgsql.Internal.TypeHandlers; +using Npgsql.Internal.TypeHandlers.DateTimeHandlers; +using Npgsql.Internal.TypeHandlers.FullTextSearchHandlers; +using Npgsql.Internal.TypeHandlers.GeometricHandlers; +using Npgsql.Internal.TypeHandlers.InternalTypeHandlers; +using Npgsql.Internal.TypeHandlers.LTreeHandlers; +using Npgsql.Internal.TypeHandlers.NetworkHandlers; +using Npgsql.Internal.TypeHandlers.NumericHandlers; +using Npgsql.Internal.TypeHandling; +using Npgsql.PostgresTypes; +using NpgsqlTypes; +using static Npgsql.Util.Statics; + +namespace Npgsql.TypeMapping; + +class BuiltInTypeHandlerResolver : TypeHandlerResolver +{ + readonly NpgsqlConnector _connector; + readonly NpgsqlDatabaseInfo _databaseInfo; + + static readonly Type ReadOnlyIPAddressType = IPAddress.Loopback.GetType(); + + static readonly Dictionary Mappings = new() + { + // Numeric types + { "smallint", new(NpgsqlDbType.Smallint, "smallint", typeof(short), typeof(byte), typeof(sbyte)) }, + { "integer", new(NpgsqlDbType.Integer, "integer", typeof(int)) }, + { "int", new(NpgsqlDbType.Integer, "integer", typeof(int)) }, + { "bigint", new(NpgsqlDbType.Bigint, "bigint", typeof(long)) }, + { "real", new(NpgsqlDbType.Real, "real", typeof(float)) }, + { "double precision", new(NpgsqlDbType.Double, "double precision", typeof(double)) }, + { "numeric", new(NpgsqlDbType.Numeric, "numeric", typeof(decimal), typeof(BigInteger)) }, + { "decimal", new(NpgsqlDbType.Numeric, "numeric", typeof(decimal), typeof(BigInteger)) }, + { "money", new(NpgsqlDbType.Money, "money") }, + + // Text types + { "text", new(NpgsqlDbType.Text, "text", typeof(string), typeof(char[]), typeof(char), typeof(ArraySegment)) }, + { "xml", new(NpgsqlDbType.Xml, "xml") }, + { "character varying", new(NpgsqlDbType.Varchar, "character varying") }, + { "varchar", new(NpgsqlDbType.Varchar, "character varying") }, + { "character", new(NpgsqlDbType.Char, "character") }, + { "name", new(NpgsqlDbType.Name, "name") }, + { "refcursor", new(NpgsqlDbType.Refcursor, "refcursor") }, + { "citext", new(NpgsqlDbType.Citext, "citext") }, + { "jsonb", new(NpgsqlDbType.Jsonb, "jsonb", typeof(JsonDocument)) }, + { "json", new(NpgsqlDbType.Json, "json") }, + { "jsonpath", new(NpgsqlDbType.JsonPath, "jsonpath") }, + + // Date/time types +#pragma warning disable 618 // NpgsqlDateTime is obsolete, remove in 7.0 + { "timestamp without time zone", new(NpgsqlDbType.Timestamp, "timestamp without time zone", typeof(DateTime), typeof(NpgsqlDateTime)) }, + { "timestamp", new(NpgsqlDbType.Timestamp, "timestamp without time zone", typeof(DateTime), typeof(NpgsqlDateTime)) }, +#pragma warning disable 618 + { "timestamp with time zone", new(NpgsqlDbType.TimestampTz, "timestamp with time zone", typeof(DateTimeOffset)) }, + { "timestamptz", new(NpgsqlDbType.TimestampTz, "timestamp with time zone", typeof(DateTimeOffset)) }, + { "date", new(NpgsqlDbType.Date, "date", typeof(NpgsqlDate) +#if NET6_0_OR_GREATER + , typeof(DateOnly) +#endif + ) }, + { "time without time zone", new(NpgsqlDbType.Time, "time without time zone" +#if NET6_0_OR_GREATER + , typeof(TimeOnly) +#endif + ) }, + { "time", new(NpgsqlDbType.Time, "time without time zone" +#if NET6_0_OR_GREATER + , typeof(TimeOnly) +#endif + ) }, + { "time with time zone", new(NpgsqlDbType.TimeTz, "time with time zone") }, + { "timetz", new(NpgsqlDbType.TimeTz, "time with time zone") }, + { "interval", new(NpgsqlDbType.Interval, "interval", typeof(TimeSpan), typeof(NpgsqlTimeSpan)) }, + + { "timestamp without time zone[]", new(NpgsqlDbType.Array | NpgsqlDbType.Timestamp, "timestamp without time zone[]") }, + { "timestamp with time zone[]", new(NpgsqlDbType.Array | NpgsqlDbType.TimestampTz, "timestamp with time zone[]") }, + + { "int4range", new(NpgsqlDbType.IntegerRange, "int4range") }, + { "int8range", new(NpgsqlDbType.BigIntRange, "int8range") }, + { "numrange", new(NpgsqlDbType.NumericRange, "numrange") }, + { "daterange", new(NpgsqlDbType.DateRange, "daterange") }, + { "tsrange", new(NpgsqlDbType.TimestampRange, "tsrange") }, + { "tstzrange", new(NpgsqlDbType.TimestampTzRange, "tstzrange") }, + + { "int4multirange", new(NpgsqlDbType.IntegerMultirange, "int4range") }, + { "int8multirange", new(NpgsqlDbType.BigIntMultirange, "int8range") }, + { "nummultirange", new(NpgsqlDbType.NumericMultirange, "numrange") }, + { "datemultirange", new(NpgsqlDbType.DateMultirange, "datemultirange") }, + { "tsmultirange", new(NpgsqlDbType.TimestampMultirange, "tsmultirange") }, + { "tstzmultirange", new(NpgsqlDbType.TimestampTzMultirange, "tstzmultirange") }, + + // Network types + { "cidr", new(NpgsqlDbType.Cidr, "cidr") }, +#pragma warning disable 618 + { "inet", new(NpgsqlDbType.Inet, "inet", typeof(IPAddress), typeof((IPAddress Address, int Subnet)), typeof(NpgsqlInet), ReadOnlyIPAddressType) }, +#pragma warning restore 618 + { "macaddr", new(NpgsqlDbType.MacAddr, "macaddr", typeof(PhysicalAddress)) }, + { "macaddr8", new(NpgsqlDbType.MacAddr8, "macaddr8") }, + + // Full-text search types + { "tsquery", new(NpgsqlDbType.TsQuery, "tsquery", + typeof(NpgsqlTsQuery), typeof(NpgsqlTsQueryAnd), typeof(NpgsqlTsQueryEmpty), typeof(NpgsqlTsQueryFollowedBy), + typeof(NpgsqlTsQueryLexeme), typeof(NpgsqlTsQueryNot), typeof(NpgsqlTsQueryOr), typeof(NpgsqlTsQueryBinOp) + ) }, + { "tsvector", new(NpgsqlDbType.TsVector, "tsvector", typeof(NpgsqlTsVector)) }, + + // Geometry types + { "box", new(NpgsqlDbType.Box, "box", typeof(NpgsqlBox)) }, + { "circle", new(NpgsqlDbType.Circle, "circle", typeof(NpgsqlCircle)) }, + { "line", new(NpgsqlDbType.Line, "line", typeof(NpgsqlLine)) }, + { "lseg", new(NpgsqlDbType.LSeg, "lseg", typeof(NpgsqlLSeg)) }, + { "path", new(NpgsqlDbType.Path, "path", typeof(NpgsqlPath)) }, + { "point", new(NpgsqlDbType.Point, "point", typeof(NpgsqlPoint)) }, + { "polygon", new(NpgsqlDbType.Polygon, "polygon", typeof(NpgsqlPolygon)) }, + + // LTree types + { "lquery", new(NpgsqlDbType.LQuery, "lquery") }, + { "ltree", new(NpgsqlDbType.LTree, "ltree") }, + { "ltxtquery", new(NpgsqlDbType.LTxtQuery, "ltxtquery") }, + + // UInt types + { "oid", new(NpgsqlDbType.Oid, "oid") }, + { "xid", new(NpgsqlDbType.Xid, "xid") }, + { "xid8", new(NpgsqlDbType.Xid8, "xid8") }, + { "cid", new(NpgsqlDbType.Cid, "cid") }, + { "regtype", new(NpgsqlDbType.Regtype, "regtype") }, + { "regconfig", new(NpgsqlDbType.Regconfig, "regconfig") }, + + // Misc types + { "boolean", new(NpgsqlDbType.Boolean, "boolean", typeof(bool)) }, + { "bool", new(NpgsqlDbType.Boolean, "boolean", typeof(bool)) }, + { "bytea", new(NpgsqlDbType.Bytea, "bytea", typeof(byte[]), typeof(ArraySegment) +#if !NETSTANDARD2_0 + , typeof(ReadOnlyMemory), typeof(Memory) +#endif + ) }, + { "uuid", new(NpgsqlDbType.Uuid, "uuid", typeof(Guid)) }, + { "bit varying", new(NpgsqlDbType.Varbit, "bit varying", typeof(BitArray), typeof(BitVector32)) }, + { "varbit", new(NpgsqlDbType.Varbit, "bit varying", typeof(BitArray), typeof(BitVector32)) }, + { "bit", new(NpgsqlDbType.Bit, "bit") }, + { "hstore", new(NpgsqlDbType.Hstore, "hstore", typeof(Dictionary), typeof(IDictionary) +#if !NETSTANDARD2_0 && !NETSTANDARD2_1 + , typeof(ImmutableDictionary) +#endif + ) }, + + // Internal types + { "int2vector", new(NpgsqlDbType.Int2Vector, "int2vector") }, + { "oidvector", new(NpgsqlDbType.Oidvector, "oidvector") }, + { "pg_lsn", new(NpgsqlDbType.PgLsn, "pg_lsn", typeof(NpgsqlLogSequenceNumber)) }, + { "tid", new(NpgsqlDbType.Tid, "tid", typeof(NpgsqlTid)) }, + { "char", new(NpgsqlDbType.InternalChar, "char") }, + + // Special types + { "unknown", new(NpgsqlDbType.Unknown, "unknown") }, + }; + + #region Cached handlers + + // Numeric types + readonly Int16Handler _int16Handler; + readonly Int32Handler _int32Handler; + readonly Int64Handler _int64Handler; + SingleHandler? _singleHandler; + readonly DoubleHandler _doubleHandler; + readonly NumericHandler _numericHandler; + MoneyHandler? _moneyHandler; + + // Text types + readonly TextHandler _textHandler; + TextHandler? _xmlHandler; + TextHandler? _varcharHandler; + TextHandler? _charHandler; + TextHandler? _nameHandler; + TextHandler? _refcursorHandler; + TextHandler? _citextHandler; + JsonHandler? _jsonbHandler; // Note that old version of PG (and Redshift) don't have jsonb + JsonHandler? _jsonHandler; + JsonPathHandler? _jsonPathHandler; + + // Date/time types + readonly TimestampHandler _timestampHandler; + readonly TimestampTzHandler _timestampTzHandler; + readonly DateHandler _dateHandler; + TimeHandler? _timeHandler; + TimeTzHandler? _timeTzHandler; + IntervalHandler? _intervalHandler; + + // Network types + CidrHandler? _cidrHandler; + InetHandler? _inetHandler; + MacaddrHandler? _macaddrHandler; + MacaddrHandler? _macaddr8Handler; + + // Full-text search types + TsQueryHandler? _tsQueryHandler; + TsVectorHandler? _tsVectorHandler; + + // Geometry types + BoxHandler? _boxHandler; + CircleHandler? _circleHandler; + LineHandler? _lineHandler; + LineSegmentHandler? _lineSegmentHandler; + PathHandler? _pathHandler; + PointHandler? _pointHandler; + PolygonHandler? _polygonHandler; + + // LTree types + LQueryHandler? _lQueryHandler; + LTreeHandler? _lTreeHandler; + LTxtQueryHandler? _lTxtQueryHandler; + + // UInt types + UInt32Handler? _oidHandler; + UInt32Handler? _xidHandler; + UInt64Handler? _xid8Handler; + UInt32Handler? _cidHandler; + UInt32Handler? _regtypeHandler; + UInt32Handler? _regconfigHandler; + + // Misc types + readonly BoolHandler _boolHandler; + ByteaHandler? _byteaHandler; + UuidHandler? _uuidHandler; + BitStringHandler? _bitVaryingHandler; + BitStringHandler? _bitHandler; + RecordHandler? _recordHandler; + VoidHandler? _voidHandler; + HstoreHandler? _hstoreHandler; + + // Internal types + Int2VectorHandler? _int2VectorHandler; + OIDVectorHandler? _oidVectorHandler; + PgLsnHandler? _pgLsnHandler; + TidHandler? _tidHandler; + InternalCharHandler? _internalCharHandler; + + // Special types + UnknownTypeHandler? _unknownHandler; + + // Complex type handlers over timestamp/timestamptz (because DateTime is value-dependent) + NpgsqlTypeHandler? _timestampArrayHandler; + NpgsqlTypeHandler? _timestampTzArrayHandler; + NpgsqlTypeHandler? _timestampRangeHandler; + NpgsqlTypeHandler? _timestampTzRangeHandler; + NpgsqlTypeHandler? _timestampMultirangeHandler; + NpgsqlTypeHandler? _timestampTzMultirangeHandler; + + #endregion Cached handlers + + internal BuiltInTypeHandlerResolver(NpgsqlConnector connector) + { + _connector = connector; + _databaseInfo = connector.DatabaseInfo; + + // Eagerly instantiate some handlers for very common types so we don't need to check later + _int16Handler = new Int16Handler(PgType("smallint")); + _int32Handler = new Int32Handler(PgType("integer")); + _int64Handler = new Int64Handler(PgType("bigint")); + _doubleHandler = new DoubleHandler(PgType("double precision")); + _numericHandler = new NumericHandler(PgType("numeric")); + _textHandler ??= new TextHandler(PgType("text"), _connector.TextEncoding); + _timestampHandler ??= new TimestampHandler(PgType("timestamp without time zone")); + _timestampTzHandler ??= new TimestampTzHandler(PgType("timestamp with time zone")); + _dateHandler ??= new DateHandler(PgType("date")); + _boolHandler ??= new BoolHandler(PgType("boolean")); + } + + public override NpgsqlTypeHandler? ResolveByDataTypeName(string typeName) + => typeName switch + { + // Numeric types + "smallint" => _int16Handler, + "integer" or "int" => _int32Handler, + "bigint" => _int64Handler, + "real" => SingleHandler(), + "double precision" => _doubleHandler, + "numeric" or "decimal" => _numericHandler, + "money" => MoneyHandler(), + + // Text types + "text" => _textHandler, + "xml" => XmlHandler(), + "varchar" or "character varying" => VarcharHandler(), + "character" => CharHandler(), + "name" => NameHandler(), + "refcursor" => RefcursorHandler(), + "citext" => CitextHandler(), + "jsonb" => JsonbHandler(), + "json" => JsonHandler(), + "jsonpath" => JsonPathHandler(), + + // Date/time types + "timestamp" or "timestamp without time zone" => _timestampHandler, + "timestamptz" or "timestamp with time zone" => _timestampTzHandler, + "date" => _dateHandler, + "time without time zone" => TimeHandler(), + "time with time zone" => TimeTzHandler(), + "interval" => IntervalHandler(), + + // Network types + "cidr" => CidrHandler(), + "inet" => InetHandler(), + "macaddr" => MacaddrHandler(), + "macaddr8" => Macaddr8Handler(), + + // Full-text search types + "tsquery" => TsQueryHandler(), + "tsvector" => TsVectorHandler(), + + // Geometry types + "box" => BoxHandler(), + "circle" => CircleHandler(), + "line" => LineHandler(), + "lseg" => LineSegmentHandler(), + "path" => PathHandler(), + "point" => PointHandler(), + "polygon" => PolygonHandler(), + + // LTree types + "lquery" => LQueryHandler(), + "ltree" => LTreeHandler(), + "ltxtquery" => LTxtHandler(), + + // UInt types + "oid" => OidHandler(), + "xid" => XidHandler(), + "xid8" => Xid8Handler(), + "cid" => CidHandler(), + "regtype" => RegtypeHandler(), + "regconfig" => RegconfigHandler(), + + // Misc types + "bool" or "boolean" => _boolHandler, + "bytea" => ByteaHandler(), + "uuid" => UuidHandler(), + "bit varying" or "varbit" => BitVaryingHandler(), + "bit" => BitHandler(), + "hstore" => HstoreHandler(), + + // Internal types + "int2vector" => Int2VectorHandler(), + "oidvector" => OidVectorHandler(), + "pg_lsn" => PgLsnHandler(), + "tid" => TidHandler(), + "char" => InternalCharHandler(), + "record" => RecordHandler(), + "void" => VoidHandler(), + + "unknown" => UnknownHandler(), + + _ => null + }; + + public override NpgsqlTypeHandler? ResolveByClrType(Type type) + => ClrTypeToDataTypeNameTable.TryGetValue(type, out var dataTypeName) && ResolveByDataTypeName(dataTypeName) is { } handler + ? handler + : null; + + static readonly Dictionary ClrTypeToDataTypeNameTable; + + static BuiltInTypeHandlerResolver() + { + ClrTypeToDataTypeNameTable = new() + { + // Numeric types + { typeof(byte), "smallint" }, + { typeof(short), "smallint" }, + { typeof(int), "integer" }, + { typeof(long), "bigint" }, + { typeof(float), "real" }, + { typeof(double), "double precision" }, + { typeof(decimal), "decimal" }, + { typeof(BigInteger), "decimal" }, + + // Text types + { typeof(string), "text" }, + { typeof(char[]), "text" }, + { typeof(char), "text" }, + { typeof(ArraySegment), "text" }, + { typeof(JsonDocument), "jsonb" }, + + // Date/time types + // The DateTime entry is for LegacyTimestampBehavior mode only. In regular mode we resolve through + // ResolveValueDependentValue below + { typeof(DateTime), "timestamp without time zone" }, + { typeof(DateTimeOffset), "timestamp with time zone" }, +#if NET6_0_OR_GREATER + { typeof(DateOnly), "date" }, + { typeof(TimeOnly), "time without time zone" }, +#endif + { typeof(TimeSpan), "interval" }, + { typeof(NpgsqlInterval), "interval" }, +#pragma warning disable 618 // NpgsqlDateTime and NpgsqlDate are obsolete, remove in 7.0 + { typeof(NpgsqlDateTime), "timestamp without time zone" }, + { typeof(NpgsqlDate), "date" }, + { typeof(NpgsqlTimeSpan), "interval" }, +#pragma warning restore 618 + + // Network types + { typeof(IPAddress), "inet" }, + // See ReadOnlyIPAddress below + { typeof((IPAddress Address, int Subnet)), "inet" }, +#pragma warning disable 618 + { typeof(NpgsqlInet), "inet" }, +#pragma warning restore 618 + { typeof(PhysicalAddress), "macaddr" }, + + // Full-text types + { typeof(NpgsqlTsVector), "tsvector" }, + { typeof(NpgsqlTsQueryLexeme), "tsquery" }, + { typeof(NpgsqlTsQueryAnd), "tsquery" }, + { typeof(NpgsqlTsQueryOr), "tsquery" }, + { typeof(NpgsqlTsQueryNot), "tsquery" }, + { typeof(NpgsqlTsQueryEmpty), "tsquery" }, + { typeof(NpgsqlTsQueryFollowedBy), "tsquery" }, + + // Geometry types + { typeof(NpgsqlBox), "box" }, + { typeof(NpgsqlCircle), "circle" }, + { typeof(NpgsqlLine), "line" }, + { typeof(NpgsqlLSeg), "lseg" }, + { typeof(NpgsqlPath), "path" }, + { typeof(NpgsqlPoint), "point" }, + { typeof(NpgsqlPolygon), "polygon" }, + + // Misc types + { typeof(bool), "boolean" }, + { typeof(byte[]), "bytea" }, + { typeof(ArraySegment), "bytea" }, +#if !NETSTANDARD2_0 + { typeof(ReadOnlyMemory), "bytea" }, + { typeof(Memory), "bytea" }, +#endif + { typeof(Guid), "uuid" }, + { typeof(BitArray), "bit varying" }, + { typeof(BitVector32), "bit varying" }, + { typeof(Dictionary), "hstore" }, +#if !NETSTANDARD2_0 && !NETSTANDARD2_1 + { typeof(ImmutableDictionary), "hstore" }, +#endif + + // Internal types + { typeof(NpgsqlLogSequenceNumber), "pg_lsn" }, + { typeof(NpgsqlTid), "tid" }, + { typeof(DBNull), "unknown" }, + + // Built-in range types + { typeof(NpgsqlRange), "int4range" }, + { typeof(NpgsqlRange), "int8range" }, + { typeof(NpgsqlRange), "numrange" }, +#if NET6_0_OR_GREATER + { typeof(NpgsqlRange), "daterange" }, +#endif + + // Built-in multirange types + { typeof(NpgsqlRange[]), "int4multirange" }, + { typeof(List>), "int4multirange" }, + { typeof(NpgsqlRange[]), "int8multirange" }, + { typeof(List>), "int8multirange" }, + { typeof(NpgsqlRange[]), "nummultirange" }, + { typeof(List>), "nummultirange" }, +#if NET6_0_OR_GREATER + { typeof(NpgsqlRange[]), "datemultirange" }, + { typeof(List>), "datemultirange" }, +#endif + }; + + // Recent versions of .NET Core have an internal ReadOnlyIPAddress type (returned e.g. for IPAddress.Loopback) + // But older versions don't have it + if (ReadOnlyIPAddressType != typeof(IPAddress)) + ClrTypeToDataTypeNameTable[ReadOnlyIPAddressType] = "inet"; + + if (LegacyTimestampBehavior) + ClrTypeToDataTypeNameTable[typeof(DateTime)] = "timestamp without time zone"; + } + + public override NpgsqlTypeHandler? ResolveValueDependentValue(object value) + { + // In LegacyTimestampBehavior, DateTime isn't value-dependent, and handled above in ClrTypeToDataTypeNameTable like other types + if (LegacyTimestampBehavior) + return null; + + return value switch + { + DateTime dateTime => dateTime.Kind == DateTimeKind.Utc ? _timestampTzHandler : _timestampHandler, + + // For arrays/lists, return timestamp or timestamptz based on the kind of the first DateTime; if the user attempts to + // mix incompatible Kinds, that will fail during validation. For empty arrays it doesn't matter. + IList array => ArrayHandler(array.Count == 0 ? DateTimeKind.Unspecified : array[0].Kind), + + NpgsqlRange range => RangeHandler(!range.LowerBoundInfinite ? range.LowerBound.Kind : + !range.UpperBoundInfinite ? range.UpperBound.Kind : DateTimeKind.Unspecified), + + NpgsqlRange[] multirange => MultirangeHandler(GetMultirangeKind(multirange)), + List> multirange => MultirangeHandler(GetMultirangeKind(multirange)), + + _ => null + }; + + NpgsqlTypeHandler ArrayHandler(DateTimeKind kind) + => kind == DateTimeKind.Utc + ? _timestampTzArrayHandler ??= _timestampTzHandler.CreateArrayHandler( + (PostgresArrayType)PgType("timestamp with time zone[]"), _connector.Settings.ArrayNullabilityMode) + : _timestampArrayHandler ??= _timestampHandler.CreateArrayHandler( + (PostgresArrayType)PgType("timestamp without time zone[]"), _connector.Settings.ArrayNullabilityMode); + + NpgsqlTypeHandler RangeHandler(DateTimeKind kind) + => kind == DateTimeKind.Utc + ? _timestampTzRangeHandler ??= _timestampTzHandler.CreateRangeHandler((PostgresRangeType)PgType("tstzrange")) + : _timestampRangeHandler ??= _timestampHandler.CreateRangeHandler((PostgresRangeType)PgType("tsrange")); + + NpgsqlTypeHandler MultirangeHandler(DateTimeKind kind) + => kind == DateTimeKind.Utc + ? _timestampTzMultirangeHandler ??= _timestampTzHandler.CreateMultirangeHandler((PostgresMultirangeType)PgType("tstzmultirange")) + : _timestampMultirangeHandler ??= _timestampHandler.CreateMultirangeHandler((PostgresMultirangeType)PgType("tsmultirange")); + } + + static DateTimeKind GetRangeKind(NpgsqlRange range) + => !range.LowerBoundInfinite + ? range.LowerBound.Kind + : !range.UpperBoundInfinite + ? range.UpperBound.Kind + : DateTimeKind.Unspecified; + + static DateTimeKind GetMultirangeKind(IList> multirange) + { + for (var i = 0; i < multirange.Count; i++) + if (!multirange[i].IsEmpty) + return GetRangeKind(multirange[i]); + + return DateTimeKind.Unspecified; + } + + internal static string? ValueDependentValueToDataTypeName(object value) + { + // In LegacyTimestampBehavior, DateTime isn't value-dependent, and handled above in ClrTypeToDataTypeNameTable like other types + if (LegacyTimestampBehavior) + return null; + + return value switch + { + DateTime dateTime => dateTime.Kind == DateTimeKind.Utc ? "timestamp with time zone" : "timestamp without time zone", + + // For arrays/lists, return timestamp or timestamptz based on the kind of the first DateTime; if the user attempts to + // mix incompatible Kinds, that will fail during validation. For empty arrays it doesn't matter. + IList array => array.Count == 0 + ? "timestamp without time zone[]" + : array[0].Kind == DateTimeKind.Utc ? "timestamp with time zone[]" : "timestamp without time zone[]", + + NpgsqlRange range => GetRangeKind(range) == DateTimeKind.Utc ? "tstzrange" : "tsrange", + + NpgsqlRange[] multirange => GetMultirangeKind(multirange) == DateTimeKind.Utc ? "tstzmultirange" : "tsmultirange", + + _ => null + }; + } + + public override NpgsqlTypeHandler? ResolveValueTypeGenerically(T value) + { + // This method only ever gets called for value types, and relies on the JIT specializing the method for T by eliding all the + // type checks below. + + // Numeric types + if (typeof(T) == typeof(byte)) + return _int16Handler; + if (typeof(T) == typeof(short)) + return _int16Handler; + if (typeof(T) == typeof(int)) + return _int32Handler; + if (typeof(T) == typeof(long)) + return _int64Handler; + if (typeof(T) == typeof(float)) + return SingleHandler(); + if (typeof(T) == typeof(double)) + return _doubleHandler; + if (typeof(T) == typeof(decimal)) + return _numericHandler; + if (typeof(T) == typeof(BigInteger)) + return _numericHandler; + + // Text types + if (typeof(T) == typeof(char)) + return _textHandler; + if (typeof(T) == typeof(ArraySegment)) + return _textHandler; + if (typeof(T) == typeof(JsonDocument)) + return JsonbHandler(); + + // Date/time types + // No resolution for DateTime, since that's value-dependent (Kind) + if (typeof(T) == typeof(DateTimeOffset)) + return _timestampTzHandler; +#if NET6_0_OR_GREATER + if (typeof(T) == typeof(DateOnly)) + return _dateHandler; + if (typeof(T) == typeof(TimeOnly)) + return _timeHandler; +#endif + if (typeof(T) == typeof(TimeSpan)) + return _intervalHandler; + if (typeof(T) == typeof(NpgsqlInterval)) + return _intervalHandler; +#pragma warning disable 618 // NpgsqlDate and NpgsqlTimeSpan are obsolete, remove in 7.0 + if (typeof(T) == typeof(NpgsqlDate)) + return _dateHandler; + if (typeof(T) == typeof(NpgsqlTimeSpan)) + return _intervalHandler; +#pragma warning restore 618 + + // Network types + if (typeof(T) == typeof(IPAddress)) + return InetHandler(); + if (typeof(T) == typeof(PhysicalAddress)) + return _macaddrHandler; + if (typeof(T) == typeof(TimeSpan)) + return _intervalHandler; + + // Geometry types + if (typeof(T) == typeof(NpgsqlBox)) + return BoxHandler(); + if (typeof(T) == typeof(NpgsqlCircle)) + return CircleHandler(); + if (typeof(T) == typeof(NpgsqlLine)) + return LineHandler(); + if (typeof(T) == typeof(NpgsqlLSeg)) + return LineSegmentHandler(); + if (typeof(T) == typeof(NpgsqlPath)) + return PathHandler(); + if (typeof(T) == typeof(NpgsqlPoint)) + return PointHandler(); + if (typeof(T) == typeof(NpgsqlPolygon)) + return PolygonHandler(); + + // Misc types + if (typeof(T) == typeof(bool)) + return _boolHandler; + if (typeof(T) == typeof(Guid)) + return UuidHandler(); + if (typeof(T) == typeof(BitVector32)) + return BitVaryingHandler(); + + // Internal types + if (typeof(T) == typeof(NpgsqlLogSequenceNumber)) + return PgLsnHandler(); + if (typeof(T) == typeof(NpgsqlTid)) + return TidHandler(); + if (typeof(T) == typeof(DBNull)) + return UnknownHandler(); + + return null; + } + + internal static string? ClrTypeToDataTypeName(Type type) + => ClrTypeToDataTypeNameTable.TryGetValue(type, out var dataTypeName) ? dataTypeName : null; + + public override TypeMappingInfo? GetMappingByDataTypeName(string dataTypeName) + => DoGetMappingByDataTypeName(dataTypeName); + + internal static TypeMappingInfo? DoGetMappingByDataTypeName(string dataTypeName) + => Mappings.TryGetValue(dataTypeName, out var mapping) ? mapping : null; + + PostgresType PgType(string pgTypeName) => _databaseInfo.GetPostgresTypeByName(pgTypeName); + + #region Handler accessors + + // Numeric types + NpgsqlTypeHandler SingleHandler() => _singleHandler ??= new SingleHandler(PgType("real")); + NpgsqlTypeHandler MoneyHandler() => _moneyHandler ??= new MoneyHandler(PgType("money")); + + // Text types + NpgsqlTypeHandler XmlHandler() => _xmlHandler ??= new TextHandler(PgType("xml"), _connector.TextEncoding); + NpgsqlTypeHandler VarcharHandler() => _varcharHandler ??= new TextHandler(PgType("character varying"), _connector.TextEncoding); + NpgsqlTypeHandler CharHandler() => _charHandler ??= new TextHandler(PgType("character"), _connector.TextEncoding); + NpgsqlTypeHandler NameHandler() => _nameHandler ??= new TextHandler(PgType("name"), _connector.TextEncoding); + NpgsqlTypeHandler RefcursorHandler() => _refcursorHandler ??= new TextHandler(PgType("refcursor"), _connector.TextEncoding); + NpgsqlTypeHandler? CitextHandler() => _citextHandler ??= _databaseInfo.TryGetPostgresTypeByName("citext", out var pgType) + ? new TextHandler(pgType, _connector.TextEncoding) + : null; + NpgsqlTypeHandler JsonbHandler() => _jsonbHandler ??= new JsonHandler(PgType("jsonb"), _connector.TextEncoding, isJsonb: true); + NpgsqlTypeHandler JsonHandler() => _jsonHandler ??= new JsonHandler(PgType("json"), _connector.TextEncoding, isJsonb: false); + NpgsqlTypeHandler JsonPathHandler() => _jsonPathHandler ??= new JsonPathHandler(PgType("jsonpath"), _connector.TextEncoding); + + // Date/time types + NpgsqlTypeHandler TimeHandler() => _timeHandler ??= new TimeHandler(PgType("time without time zone")); + NpgsqlTypeHandler TimeTzHandler() => _timeTzHandler ??= new TimeTzHandler(PgType("time with time zone")); + NpgsqlTypeHandler IntervalHandler() => _intervalHandler ??= new IntervalHandler(PgType("interval")); + + // Network types + NpgsqlTypeHandler CidrHandler() => _cidrHandler ??= new CidrHandler(PgType("cidr")); + NpgsqlTypeHandler InetHandler() => _inetHandler ??= new InetHandler(PgType("inet")); + NpgsqlTypeHandler MacaddrHandler() => _macaddrHandler ??= new MacaddrHandler(PgType("macaddr")); + NpgsqlTypeHandler Macaddr8Handler() => _macaddr8Handler ??= new MacaddrHandler(PgType("macaddr8")); + + // Full-text search types + NpgsqlTypeHandler TsQueryHandler() => _tsQueryHandler ??= new TsQueryHandler(PgType("tsquery")); + NpgsqlTypeHandler TsVectorHandler() => _tsVectorHandler ??= new TsVectorHandler(PgType("tsvector")); + + // Geometry types + NpgsqlTypeHandler BoxHandler() => _boxHandler ??= new BoxHandler(PgType("box")); + NpgsqlTypeHandler CircleHandler() => _circleHandler ??= new CircleHandler(PgType("circle")); + NpgsqlTypeHandler LineHandler() => _lineHandler ??= new LineHandler(PgType("line")); + NpgsqlTypeHandler LineSegmentHandler() => _lineSegmentHandler ??= new LineSegmentHandler(PgType("lseg")); + NpgsqlTypeHandler PathHandler() => _pathHandler ??= new PathHandler(PgType("path")); + NpgsqlTypeHandler PointHandler() => _pointHandler ??= new PointHandler(PgType("point")); + NpgsqlTypeHandler PolygonHandler() => _polygonHandler ??= new PolygonHandler(PgType("polygon")); + + // LTree types + NpgsqlTypeHandler? LQueryHandler() => _lQueryHandler ??= _databaseInfo.TryGetPostgresTypeByName("lquery", out var pgType) + ? new LQueryHandler(pgType, _connector.TextEncoding) + : null; + NpgsqlTypeHandler? LTreeHandler() => _lTreeHandler ??= _databaseInfo.TryGetPostgresTypeByName("ltree", out var pgType) + ? new LTreeHandler(pgType, _connector.TextEncoding) + : null; + NpgsqlTypeHandler? LTxtHandler() => _lTxtQueryHandler ??= _databaseInfo.TryGetPostgresTypeByName("ltxtquery", out var pgType) + ? new LTxtQueryHandler(pgType, _connector.TextEncoding) + : null; + + // UInt types + NpgsqlTypeHandler OidHandler() => _oidHandler ??= new UInt32Handler(PgType("oid")); + NpgsqlTypeHandler XidHandler() => _xidHandler ??= new UInt32Handler(PgType("xid")); + NpgsqlTypeHandler Xid8Handler() => _xid8Handler ??= new UInt64Handler(PgType("xid8")); + NpgsqlTypeHandler CidHandler() => _cidHandler ??= new UInt32Handler(PgType("cid")); + NpgsqlTypeHandler RegtypeHandler() => _regtypeHandler ??= new UInt32Handler(PgType("regtype")); + NpgsqlTypeHandler RegconfigHandler() => _regconfigHandler ??= new UInt32Handler(PgType("regconfig")); + + // Misc types + NpgsqlTypeHandler ByteaHandler() => _byteaHandler ??= new ByteaHandler(PgType("bytea")); + NpgsqlTypeHandler UuidHandler() => _uuidHandler ??= new UuidHandler(PgType("uuid")); + NpgsqlTypeHandler BitVaryingHandler() => _bitVaryingHandler ??= new BitStringHandler(PgType("bit varying")); + NpgsqlTypeHandler BitHandler() => _bitHandler ??= new BitStringHandler(PgType("bit")); + NpgsqlTypeHandler? HstoreHandler() => _hstoreHandler ??= _databaseInfo.TryGetPostgresTypeByName("hstore", out var pgType) + ? new HstoreHandler(pgType, _textHandler) + : null; + + // Internal types + NpgsqlTypeHandler Int2VectorHandler() => _int2VectorHandler ??= new Int2VectorHandler(PgType("int2vector"), PgType("smallint")); + NpgsqlTypeHandler OidVectorHandler() => _oidVectorHandler ??= new OIDVectorHandler(PgType("oidvector"), PgType("oid")); + NpgsqlTypeHandler PgLsnHandler() => _pgLsnHandler ??= new PgLsnHandler(PgType("pg_lsn")); + NpgsqlTypeHandler TidHandler() => _tidHandler ??= new TidHandler(PgType("tid")); + NpgsqlTypeHandler InternalCharHandler() => _internalCharHandler ??= new InternalCharHandler(PgType("char")); + NpgsqlTypeHandler RecordHandler() => _recordHandler ??= new RecordHandler(PgType("record"), _connector.TypeMapper); + NpgsqlTypeHandler VoidHandler() => _voidHandler ??= new VoidHandler(PgType("void")); + + NpgsqlTypeHandler UnknownHandler() => _unknownHandler ??= new UnknownTypeHandler(_connector); + + #endregion Handler accessors +} diff --git a/LibExternal/Npgsql/TypeMapping/BuiltInTypeHandlerResolverFactory.cs b/LibExternal/Npgsql/TypeMapping/BuiltInTypeHandlerResolverFactory.cs new file mode 100644 index 0000000..da7443e --- /dev/null +++ b/LibExternal/Npgsql/TypeMapping/BuiltInTypeHandlerResolverFactory.cs @@ -0,0 +1,20 @@ +using System; +using Npgsql.Internal; +using Npgsql.Internal.TypeHandling; + +namespace Npgsql.TypeMapping; + +class BuiltInTypeHandlerResolverFactory : TypeHandlerResolverFactory +{ + public override TypeHandlerResolver Create(NpgsqlConnector connector) + => new BuiltInTypeHandlerResolver(connector); + + public override string? GetDataTypeNameByClrType(Type clrType) + => BuiltInTypeHandlerResolver.ClrTypeToDataTypeName(clrType); + + public override string? GetDataTypeNameByValueDependentValue(object value) + => BuiltInTypeHandlerResolver.ValueDependentValueToDataTypeName(value); + + public override TypeMappingInfo? GetMappingByDataTypeName(string dataTypeName) + => BuiltInTypeHandlerResolver.DoGetMappingByDataTypeName(dataTypeName); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/TypeMapping/ConnectorTypeMapper.cs b/LibExternal/Npgsql/TypeMapping/ConnectorTypeMapper.cs new file mode 100644 index 0000000..721cd90 --- /dev/null +++ b/LibExternal/Npgsql/TypeMapping/ConnectorTypeMapper.cs @@ -0,0 +1,711 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Data; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using Npgsql.Internal; +using Npgsql.Internal.TypeHandlers; +using Npgsql.Internal.TypeHandling; +using Npgsql.Internal.TypeMapping; +using Npgsql.Logging; +using Npgsql.PostgresTypes; +using Npgsql.Util; +using NpgsqlTypes; + +namespace Npgsql.TypeMapping; + +sealed class ConnectorTypeMapper : TypeMapperBase +{ + internal NpgsqlConnector Connector { get; } + readonly object _writeLock = new(); + + NpgsqlDatabaseInfo? _databaseInfo; + + internal NpgsqlDatabaseInfo DatabaseInfo + { + get => _databaseInfo ?? throw new InvalidOperationException("Internal error: this type mapper hasn't yet been bound to a database info object"); + set + { + // We're attempting to set the same NpgsqlDatabaseInfo as we already have. + // If so, there is no reason to reset type mappings since NpgsqlDatabaseInfo is immutable. + // This might happen with multiplexing due to sharing the same ConnectorTypeMapper between multiple connections. + if (ReferenceEquals(_databaseInfo, value)) + return; + + _databaseInfo = value; + Reset(); + } + } + + volatile TypeHandlerResolver[] _resolvers; + internal NpgsqlTypeHandler UnrecognizedTypeHandler { get; } + + readonly ConcurrentDictionary _handlersByOID = new(); + readonly ConcurrentDictionary _handlersByNpgsqlDbType = new(); + readonly ConcurrentDictionary _handlersByClrType = new(); + readonly ConcurrentDictionary _handlersByDataTypeName = new(); + + readonly Dictionary _userTypeMappings = new(); + + /// + /// Copy of at the time when this + /// mapper was created, to detect mapping changes. If changes are made to this connection's + /// mapper, the change counter is set to -1. + /// + internal int ChangeCounter { get; private set; } + + static readonly NpgsqlLogger Log = NpgsqlLogManager.CreateLogger(nameof(ConnectorTypeMapper)); + + #region Construction + + internal ConnectorTypeMapper(NpgsqlConnector connector) : base(GlobalTypeMapper.Instance.DefaultNameTranslator) + { + Connector = connector; + UnrecognizedTypeHandler = new UnknownTypeHandler(Connector); + _resolvers = Array.Empty(); + } + + #endregion Constructors + + #region Type handler lookup + + /// + /// Looks up a type handler by its PostgreSQL type's OID. + /// + /// A PostgreSQL type OID + /// A type handler that can be used to encode and decode values. + internal NpgsqlTypeHandler ResolveByOID(uint oid) + => TryResolveByOID(oid, out var result) ? result : UnrecognizedTypeHandler; + + internal bool TryResolveByOID(uint oid, [NotNullWhen(true)] out NpgsqlTypeHandler? handler) + { + if (_handlersByOID.TryGetValue(oid, out handler)) + return true; + + if (!DatabaseInfo.ByOID.TryGetValue(oid, out var pgType)) + return false; + + lock (_writeLock) + { + if ((handler = ResolveByDataTypeNameCore(pgType.FullName)) is not null) + { + _handlersByOID[oid] = handler; + return true; + } + + if ((handler = ResolveByDataTypeNameCore(pgType.Name)) is not null) + { + _handlersByOID[oid] = handler; + return true; + } + + if ((handler = ResolveComplexTypeByDataTypeName(pgType.FullName, throwOnError: false)) is not null) + { + _handlersByOID[oid] = handler; + return true; + } + + handler = null; + return false; + } + } + + internal NpgsqlTypeHandler ResolveByNpgsqlDbType(NpgsqlDbType npgsqlDbType) + { + if (_handlersByNpgsqlDbType.TryGetValue(npgsqlDbType, out var handler)) + return handler; + + lock (_writeLock) + { + // First, try to resolve as a base type; translate the NpgsqlDbType to a PG data type name and look that up. + if (GlobalTypeMapper.NpgsqlDbTypeToDataTypeName(npgsqlDbType) is { } dataTypeName) + { + foreach (var resolver in _resolvers) + { + try + { + if ((handler = resolver.ResolveByDataTypeName(dataTypeName)) is not null) + return _handlersByNpgsqlDbType[npgsqlDbType] = handler; + } + catch (Exception e) + { + Log.Error($"Type resolver {resolver.GetType().Name} threw exception while resolving NpgsqlDbType {npgsqlDbType}", + e); + } + } + } + + if (npgsqlDbType.HasFlag(NpgsqlDbType.Array)) + { + var elementHandler = ResolveByNpgsqlDbType(npgsqlDbType & ~NpgsqlDbType.Array); + + if (elementHandler.PostgresType.Array is not { } pgArrayType) + throw new ArgumentException( + $"No array type could be found in the database for element {elementHandler.PostgresType}"); + + return _handlersByNpgsqlDbType[npgsqlDbType] = + elementHandler.CreateArrayHandler(pgArrayType, Connector.Settings.ArrayNullabilityMode); + } + + if (npgsqlDbType.HasFlag(NpgsqlDbType.Range)) + { + var subtypeHandler = ResolveByNpgsqlDbType(npgsqlDbType & ~NpgsqlDbType.Range); + + if (subtypeHandler.PostgresType.Range is not { } pgRangeType) + throw new ArgumentException( + $"No range type could be found in the database for subtype {subtypeHandler.PostgresType}"); + + return _handlersByNpgsqlDbType[npgsqlDbType] = subtypeHandler.CreateRangeHandler(pgRangeType); + } + + if (npgsqlDbType.HasFlag(NpgsqlDbType.Multirange)) + { + var subtypeHandler = ResolveByNpgsqlDbType(npgsqlDbType & ~NpgsqlDbType.Multirange); + + if (subtypeHandler.PostgresType.Range?.Multirange is not { } pgMultirangeType) + throw new ArgumentException( + $"No multirange type could be found in the database for subtype {subtypeHandler.PostgresType}"); + + return _handlersByNpgsqlDbType[npgsqlDbType] = subtypeHandler.CreateMultirangeHandler(pgMultirangeType); + } + + throw new NpgsqlException($"The NpgsqlDbType '{npgsqlDbType}' isn't present in your database. " + + "You may need to install an extension or upgrade to a newer version."); + } + } + + internal NpgsqlTypeHandler ResolveByDataTypeName(string typeName) + => ResolveByDataTypeNameCore(typeName) ?? ResolveComplexTypeByDataTypeName(typeName, throwOnError: true)!; + + NpgsqlTypeHandler? ResolveByDataTypeNameCore(string typeName) + { + if (_handlersByDataTypeName.TryGetValue(typeName, out var handler)) + return handler; + + lock (_writeLock) + { + foreach (var resolver in _resolvers) + { + try + { + if ((handler = resolver.ResolveByDataTypeName(typeName)) is not null) + return _handlersByDataTypeName[typeName] = handler; + } + catch (Exception e) + { + Log.Error($"Type resolver {resolver.GetType().Name} threw exception while resolving data type name {typeName}", e); + } + } + + return null; + } + } + + NpgsqlTypeHandler? ResolveComplexTypeByDataTypeName(string typeName, bool throwOnError) + { + lock (_writeLock) + { + if (DatabaseInfo.GetPostgresTypeByName(typeName) is not { } pgType) + throw new NotSupportedException("Could not find PostgreSQL type " + typeName); + + switch (pgType) + { + case PostgresArrayType pgArrayType: + { + var elementHandler = ResolveByOID(pgArrayType.Element.OID); + return _handlersByDataTypeName[typeName] = + elementHandler.CreateArrayHandler(pgArrayType, Connector.Settings.ArrayNullabilityMode); + } + + case PostgresRangeType pgRangeType: + { + var subtypeHandler = ResolveByOID(pgRangeType.Subtype.OID); + return _handlersByDataTypeName[typeName] = subtypeHandler.CreateRangeHandler(pgRangeType); + } + + case PostgresMultirangeType pgMultirangeType: + { + var subtypeHandler = ResolveByOID(pgMultirangeType.Subrange.Subtype.OID); + return _handlersByDataTypeName[typeName] = subtypeHandler.CreateMultirangeHandler(pgMultirangeType); + } + + case PostgresEnumType pgEnumType: + { + // A mapped enum would have been registered in _extraHandlersByDataTypeName and bound above - this is unmapped. + return _handlersByDataTypeName[typeName] = + new UnmappedEnumHandler(pgEnumType, DefaultNameTranslator, Connector.TextEncoding); + } + + case PostgresDomainType pgDomainType: + return _handlersByDataTypeName[typeName] = ResolveByOID(pgDomainType.BaseType.OID); + + case PostgresBaseType pgBaseType: + return throwOnError + ? throw new NotSupportedException($"PostgreSQL type '{pgBaseType}' isn't supported by Npgsql") + : null; + + case PostgresCompositeType pgCompositeType: + // We don't support writing unmapped composite types, but we do support reading unmapped composite types. + // So when we're invoked from ResolveOID (which is the read path), we don't want to raise an exception. + return throwOnError + ? throw new NotSupportedException( + $"Composite type '{pgCompositeType}' must be mapped with Npgsql before being used, see the docs.") + : null; + + default: + throw new ArgumentOutOfRangeException($"Unhandled PostgreSQL type type: {pgType.GetType()}"); + } + } + } + + internal NpgsqlTypeHandler ResolveByValue(T value) + { + if (value is null) + return ResolveByClrType(typeof(T)); + + if (typeof(T).IsValueType) + { + // Attempt to resolve value types generically via the resolver. This is the efficient fast-path, where we don't even need to + // do a dictionary lookup (the JIT elides type checks in generic methods for value types) + NpgsqlTypeHandler? handler; + + foreach (var resolver in _resolvers) + { + try + { + if ((handler = resolver.ResolveValueTypeGenerically(value)) is not null) + return handler; + } + catch (Exception e) + { + Log.Error($"Type resolver {resolver.GetType().Name} threw exception while resolving value with type {typeof(T)}", e); + } + } + + // There may still be some value types not resolved by the above, e.g. NpgsqlRange + } + + // Value types would have been resolved above, so this is a reference type - no JIT optimizations. + // We go through the regular logic (and there's no boxing). + return ResolveByValue((object)value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal NpgsqlTypeHandler ResolveByValue(object value) + { + // We resolve as follows: + // 1. Cached by-type lookup (fast path). This will work for almost all types after the very first resolution. + // 2. Value-dependent type lookup (e.g. DateTime by Kind) via the resolvers. This includes complex types (e.g. array/range + // over DateTime), and the results cannot be cached. + // 3. Uncached by-type lookup (for the very first resolution of a given type) + + var type = value.GetType(); + if (_handlersByClrType.TryGetValue(type, out var handler)) + return handler; + + foreach (var resolver in _resolvers) + { + try + { + if ((handler = resolver.ResolveValueDependentValue(value)) is not null) + return handler; + } + catch (Exception e) + { + Log.Error($"Type resolver {resolver.GetType().Name} threw exception while resolving value with type {type}", e); + } + } + + // ResolveByClrType either throws, or resolves a handler and caches it in _handlersByClrType (where it would be found above the + // next time we resolve this type) + return ResolveByClrType(type); + } + + // TODO: This is needed as a separate method only because of binary COPY, see #3957 + internal NpgsqlTypeHandler ResolveByClrType(Type type) + { + if (_handlersByClrType.TryGetValue(type, out var handler)) + return handler; + + lock (_writeLock) + { + foreach (var resolver in _resolvers) + { + try + { + if ((handler = resolver.ResolveByClrType(type)) is not null) + return _handlersByClrType[type] = handler; + } + catch (Exception e) + { + Log.Error($"Type resolver {resolver.GetType().Name} threw exception while resolving value with type {type}", e); + } + } + + // Try to see if it is an array type + var arrayElementType = GetArrayListElementType(type); + if (arrayElementType is not null) + { + // With PG14, we map arrays over range types to PG multiranges by default, not to regular arrays over ranges. + if (arrayElementType.IsGenericType && + arrayElementType.GetGenericTypeDefinition() == typeof(NpgsqlRange<>) && + DatabaseInfo.Version.IsGreaterOrEqual(14)) + { + var subtypeType = arrayElementType.GetGenericArguments()[0]; + + return ResolveByClrType(subtypeType) is + { PostgresType : { Range : { Multirange: { } pgMultirangeType } } } subtypeHandler + ? _handlersByClrType[type] = subtypeHandler.CreateMultirangeHandler(pgMultirangeType) + : throw new NotSupportedException($"The CLR range type {type} isn't supported by Npgsql or your PostgreSQL."); + } + + if (ResolveByClrType(arrayElementType) is not { } elementHandler) + throw new ArgumentException($"Array type over CLR type {arrayElementType.Name} isn't supported by Npgsql"); + + if (elementHandler.PostgresType.Array is not { } pgArrayType) + throw new ArgumentException( + $"No array type could be found in the database for element {elementHandler.PostgresType}"); + + return _handlersByClrType[type] = + elementHandler.CreateArrayHandler(pgArrayType, Connector.Settings.ArrayNullabilityMode); + } + + if (Nullable.GetUnderlyingType(type) is { } underlyingType && ResolveByClrType(underlyingType) is { } underlyingHandler) + return _handlersByClrType[type] = underlyingHandler; + + if (type.IsEnum) + { + return DatabaseInfo.GetPostgresTypeByName(GetPgName(type, DefaultNameTranslator)) is PostgresEnumType pgEnumType + ? _handlersByClrType[type] = new UnmappedEnumHandler(pgEnumType, DefaultNameTranslator, Connector.TextEncoding) + : throw new NotSupportedException( + $"Could not find a PostgreSQL enum type corresponding to {type.Name}. " + + "Consider mapping the enum before usage, refer to the documentation for more details."); + } + + // TODO: We can make the following compatible with reflection-free mode by having NpgsqlRange implement some interface, and + // check for that. + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(NpgsqlRange<>)) + { + var subtypeType = type.GetGenericArguments()[0]; + + return ResolveByClrType(subtypeType) is { PostgresType : { Range : { } pgRangeType } } subtypeHandler + ? _handlersByClrType[type] = subtypeHandler.CreateRangeHandler(pgRangeType) + : throw new NotSupportedException($"The CLR range type {type} isn't supported by Npgsql or your PostgreSQL."); + } + + if (typeof(IEnumerable).IsAssignableFrom(type)) + throw new NotSupportedException("IEnumerable parameters are not supported, pass an array or List instead"); + + throw new NotSupportedException($"The CLR type {type} isn't natively supported by Npgsql or your PostgreSQL. " + + $"To use it with a PostgreSQL composite you need to specify {nameof(NpgsqlParameter.DataTypeName)} or to map it, please refer to the documentation."); + } + + static Type? GetArrayListElementType(Type type) + { + var typeInfo = type.GetTypeInfo(); + if (typeInfo.IsArray) + return GetUnderlyingType(type.GetElementType()!); // The use of bang operator is justified here as Type.GetElementType() only returns null for the Array base class which can't be mapped in a useful way. + + var ilist = typeInfo.ImplementedInterfaces.FirstOrDefault(x => x.GetTypeInfo().IsGenericType && x.GetGenericTypeDefinition() == typeof(IList<>)); + if (ilist != null) + return GetUnderlyingType(ilist.GetGenericArguments()[0]); + + if (typeof(IList).IsAssignableFrom(type)) + throw new NotSupportedException("Non-generic IList is a supported parameter, but the NpgsqlDbType parameter must be set on the parameter"); + + return null; + + Type GetUnderlyingType(Type t) + => Nullable.GetUnderlyingType(t) ?? t; + } + } + + internal bool TryGetMapping(PostgresType pgType, [NotNullWhen(true)] out TypeMappingInfo? mapping) + { + foreach (var resolver in _resolvers) + if ((mapping = resolver.GetMappingByDataTypeName(pgType.FullName)) is not null) + return true; + + foreach (var resolver in _resolvers) + if ((mapping = resolver.GetMappingByDataTypeName(pgType.Name)) is not null) + return true; + + switch (pgType) + { + case PostgresArrayType pgArrayType: + if (TryGetMapping(pgArrayType.Element, out var elementMapping)) + { + mapping = new(elementMapping.NpgsqlDbType | NpgsqlDbType.Array, pgType.DisplayName); + return true; + } + + break; + + case PostgresRangeType pgRangeType: + { + if (TryGetMapping(pgRangeType.Subtype, out var subtypeMapping)) + { + mapping = new(subtypeMapping.NpgsqlDbType | NpgsqlDbType.Range, pgType.DisplayName); + return true; + } + + break; + } + + case PostgresMultirangeType pgMultirangeType: + { + if (TryGetMapping(pgMultirangeType.Subrange.Subtype, out var subtypeMapping)) + { + mapping = new(subtypeMapping.NpgsqlDbType | NpgsqlDbType.Multirange, pgType.DisplayName); + return true; + } + + break; + } + + case PostgresDomainType pgDomainType: + if (TryGetMapping(pgDomainType.BaseType, out var baseMapping)) + { + mapping = new(baseMapping.NpgsqlDbType, pgType.DisplayName, baseMapping.ClrTypes); + return true; + } + + break; + + case PostgresEnumType or PostgresCompositeType: + return _userTypeMappings.TryGetValue(pgType.OID, out mapping); + } + + mapping = null; + return false; + } + + #endregion Type handler lookup + + #region Mapping management + + public override INpgsqlTypeMapper MapEnum(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) + { + if (pgName != null && pgName.Trim() == "") + throw new ArgumentException("pgName can't be empty", nameof(pgName)); + + nameTranslator ??= DefaultNameTranslator; + pgName ??= GetPgName(typeof(TEnum), nameTranslator); + + if (DatabaseInfo.GetPostgresTypeByName(pgName) is not PostgresEnumType pgEnumType) + throw new InvalidCastException($"Cannot map enum type {typeof(TEnum).Name} to PostgreSQL type {pgName} which isn't an enum"); + + var handler = new UserEnumTypeMapping(pgName, nameTranslator).CreateHandler(pgEnumType, Connector); + + ApplyUserMapping(pgEnumType, typeof(TEnum), handler); + + return this; + } + + public override bool UnmapEnum(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) + { + if (pgName != null && pgName.Trim() == "") + throw new ArgumentException("pgName can't be empty", nameof(pgName)); + + nameTranslator ??= DefaultNameTranslator; + pgName ??= GetPgName(typeof(TEnum), nameTranslator); + + var userEnumMapping = new UserEnumTypeMapping(pgName, nameTranslator); + + if (DatabaseInfo.GetPostgresTypeByName(pgName) is not PostgresEnumType pgEnumType) + throw new InvalidCastException($"Could not find {pgName}"); + + var found = _handlersByOID.TryRemove(pgEnumType.OID, out _); + found |= _handlersByClrType.TryRemove(userEnumMapping.ClrType, out _); + found |= _handlersByDataTypeName.TryRemove(userEnumMapping.PgTypeName, out _); + return found; + } + + public override INpgsqlTypeMapper MapComposite(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) + { + if (pgName != null && pgName.Trim() == "") + throw new ArgumentException("pgName can't be empty", nameof(pgName)); + + nameTranslator ??= DefaultNameTranslator; + pgName ??= GetPgName(typeof(T), nameTranslator); + + if (DatabaseInfo.GetPostgresTypeByName(pgName) is not PostgresCompositeType pgCompositeType) + { + throw new InvalidCastException( + $"Cannot map composite type {typeof(T).Name} to PostgreSQL type {pgName} which isn't a composite"); + } + + var handler = new UserCompositeTypeMapping(pgName, nameTranslator).CreateHandler(pgCompositeType, Connector); + + ApplyUserMapping(pgCompositeType, typeof(T), handler); + + return this; + } + + public override INpgsqlTypeMapper MapComposite(Type clrType, string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) + { + var openMethod = + typeof(ConnectorTypeMapper).GetMethod(nameof(MapComposite), new[] { typeof(string), typeof(INpgsqlNameTranslator) })!; + var method = openMethod.MakeGenericMethod(clrType); + + method.Invoke(this, new object?[] { pgName, nameTranslator }); + + return this; + } + + public override bool UnmapComposite(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) + => UnmapComposite(typeof(T), pgName, nameTranslator); + + public override bool UnmapComposite(Type clrType, string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) + { + if (pgName != null && pgName.Trim() == "") + throw new ArgumentException("pgName can't be empty", nameof(pgName)); + + nameTranslator ??= DefaultNameTranslator; + pgName ??= GetPgName(clrType, nameTranslator); + + if (DatabaseInfo.GetPostgresTypeByName(pgName) is not PostgresCompositeType pgCompositeType) + throw new InvalidCastException($"Could not find {pgName}"); + + var found = _handlersByOID.TryRemove(pgCompositeType.OID, out _); + found |= _handlersByClrType.TryRemove(clrType, out _); + found |= _handlersByDataTypeName.TryRemove(pgName, out _); + return found; + } + + void ApplyUserMapping(PostgresType pgType, Type clrType, NpgsqlTypeHandler handler) + { + _handlersByOID[pgType.OID] = + _handlersByDataTypeName[pgType.FullName] = + _handlersByDataTypeName[pgType.Name] = + _handlersByClrType[clrType] = handler; + + _userTypeMappings[pgType.OID] = new(npgsqlDbType: null, pgType.Name, clrType); + } + + public override void AddTypeResolverFactory(TypeHandlerResolverFactory resolverFactory) + { + lock (this) + { + // Since EFCore.PG plugins (and possibly other users) repeatedly call NpgsqlConnection.GlobalTypeMapped.UseNodaTime, + // we replace an existing resolver of the same CLR type. + + var newResolver = resolverFactory.Create(Connector); + var newResolverType = newResolver.GetType(); + + var currentResolvers = _resolvers; + Debug.Assert(currentResolvers.Length > 0); + Debug.Assert(currentResolvers[0] is not null); + + if (currentResolvers[0].GetType() == newResolverType) + currentResolvers[0] = newResolver; + else + { + // The TypeHandlerResolver we're attempting to add isn't the first resolver, + // but it still might be at some other position. + // We assume that that specific TypeHandlerResolver is already there + // and create a new TypeHandlerResolver array with the same length as the original one. + // It's going to be enough if we're just shuffling TypeHandlerResolver's around. + // In the worst case scenario (that's an entirely new TypeHandlerResolver), we would have to resize the array. + var resolvers = new TypeHandlerResolver[currentResolvers.Length]; + resolvers[0] = newResolver; + + var resolverIndex = 1; + for (var i = 0; i < currentResolvers.Length; i++) + { + if (currentResolvers[i].GetType() != newResolverType) + { + // Worst case scenario: we're attempting to add an entirely new TypeHandlerResolver. + // We have to resize the resolvers array to not get out of bounds. + if (i == currentResolvers.Length - 1 && resolverIndex == currentResolvers.Length) + Array.Resize(ref resolvers, currentResolvers.Length + 1); + + resolvers[resolverIndex++] = currentResolvers[i]; + } + } + + _resolvers = resolvers; + } + + _handlersByOID.Clear(); + _handlersByNpgsqlDbType.Clear(); + _handlersByClrType.Clear(); + _handlersByDataTypeName.Clear(); + + ChangeCounter = -1; + } + } + + public override void Reset() + { + lock (this) + { + var globalMapper = GlobalTypeMapper.Instance; + globalMapper.Lock.EnterReadLock(); + try + { + var resolvers = new TypeHandlerResolver[globalMapper.ResolverFactories.Count]; + for (var i = 0; i < globalMapper.ResolverFactories.Count; i++) + resolvers[i] = globalMapper.ResolverFactories[i].Create(Connector); + _resolvers = resolvers; + + _handlersByOID.Clear(); + _handlersByNpgsqlDbType.Clear(); + _handlersByClrType.Clear(); + _handlersByDataTypeName.Clear(); + + _userTypeMappings.Clear(); + + foreach (var userTypeMapping in globalMapper.UserTypeMappings.Values) + { + if (DatabaseInfo.TryGetPostgresTypeByName(userTypeMapping.PgTypeName, out var pgType)) + { + ApplyUserMapping(pgType, userTypeMapping.ClrType, userTypeMapping.CreateHandler(pgType, Connector)); + } + } + + ChangeCounter = globalMapper.ChangeCounter; + } + finally + { + globalMapper.Lock.ExitReadLock(); + } + } + } + + #endregion Mapping management + + internal (NpgsqlDbType? npgsqlDbType, PostgresType postgresType) GetTypeInfoByOid(uint oid) + { + if (!DatabaseInfo.ByOID.TryGetValue(oid, out var pgType)) + throw new InvalidOperationException($"Couldn't find PostgreSQL type with OID {oid}"); + + foreach (var resolver in _resolvers) + if (resolver.GetMappingByDataTypeName(pgType.FullName) is { } mapping) + return (mapping.NpgsqlDbType, pgType); + + foreach (var resolver in _resolvers) + if (resolver.GetMappingByDataTypeName(pgType.Name) is { } mapping) + return (mapping.NpgsqlDbType, pgType); + + switch (pgType) + { + case PostgresArrayType pgArrayType: + var (elementNpgsqlDbType, _) = GetTypeInfoByOid(pgArrayType.Element.OID); + if (elementNpgsqlDbType.HasValue) + return new(elementNpgsqlDbType | NpgsqlDbType.Array, pgType); + break; + + case PostgresDomainType pgDomainType: + var (baseNpgsqlDbType, _) = GetTypeInfoByOid(pgDomainType.BaseType.OID); + return new(baseNpgsqlDbType, pgType); + } + + return (null, pgType); + } +} diff --git a/LibExternal/Npgsql/TypeMapping/GlobalTypeMapper.cs b/LibExternal/Npgsql/TypeMapping/GlobalTypeMapper.cs new file mode 100644 index 0000000..f40bf00 --- /dev/null +++ b/LibExternal/Npgsql/TypeMapping/GlobalTypeMapper.cs @@ -0,0 +1,595 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Data; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using System.Threading; +using Npgsql.Internal.TypeHandling; +using Npgsql.Internal.TypeMapping; +using Npgsql.NameTranslation; +using NpgsqlTypes; +using static Npgsql.Util.Statics; + +namespace Npgsql.TypeMapping; + +sealed class GlobalTypeMapper : TypeMapperBase +{ + public static GlobalTypeMapper Instance { get; } + + internal List ResolverFactories { get; } = new(); + public ConcurrentDictionary UserTypeMappings { get; } = new(); + + readonly ConcurrentDictionary _mappingsByClrType = new(); + + /// + /// A counter that is incremented whenever a global mapping change occurs. + /// Used to invalidate bound type mappers. + /// + internal int ChangeCounter => _changeCounter; + + internal ReaderWriterLockSlim Lock { get; } + = new(LockRecursionPolicy.SupportsRecursion); + + int _changeCounter; + + static GlobalTypeMapper() + => Instance = new GlobalTypeMapper(); + + GlobalTypeMapper() : base(new NpgsqlSnakeCaseNameTranslator()) + => Reset(); + + #region Mapping management + + public override INpgsqlTypeMapper MapEnum(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) + { + if (pgName != null && pgName.Trim() == "") + throw new ArgumentException("pgName can't be empty", nameof(pgName)); + + nameTranslator ??= DefaultNameTranslator; + pgName ??= GetPgName(typeof(TEnum), nameTranslator); + + Lock.EnterWriteLock(); + try + { + UserTypeMappings[pgName] = new UserEnumTypeMapping(pgName, nameTranslator); + RecordChange(); + return this; + } + finally + { + Lock.ExitWriteLock(); + } + } + + public override bool UnmapEnum(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) + { + if (pgName != null && pgName.Trim() == "") + throw new ArgumentException("pgName can't be empty", nameof(pgName)); + + nameTranslator ??= DefaultNameTranslator; + pgName ??= GetPgName(typeof(TEnum), nameTranslator); + + Lock.EnterWriteLock(); + try + { + if (UserTypeMappings.TryRemove(pgName, out _)) + { + RecordChange(); + return true; + } + + return false; + } + finally + { + Lock.ExitWriteLock(); + } + } + + public override INpgsqlTypeMapper MapComposite(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) + { + if (pgName != null && pgName.Trim() == "") + throw new ArgumentException("pgName can't be empty", nameof(pgName)); + + nameTranslator ??= DefaultNameTranslator; + pgName ??= GetPgName(typeof(T), nameTranslator); + + Lock.EnterWriteLock(); + try + { + UserTypeMappings[pgName] = new UserCompositeTypeMapping(pgName, nameTranslator); + RecordChange(); + return this; + } + finally + { + Lock.ExitWriteLock(); + } + } + + public override INpgsqlTypeMapper MapComposite(Type clrType, string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) + { + var openMethod = typeof(GlobalTypeMapper).GetMethod(nameof(MapComposite), new[] { typeof(string), typeof(INpgsqlNameTranslator) })!; + var method = openMethod.MakeGenericMethod(clrType); + method.Invoke(this, new object?[] { pgName, nameTranslator }); + + return this; + } + + public override bool UnmapComposite(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) + => UnmapComposite(typeof(T), pgName, nameTranslator); + + public override bool UnmapComposite(Type clrType, string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) + { + if (pgName != null && pgName.Trim() == "") + throw new ArgumentException("pgName can't be empty", nameof(pgName)); + + nameTranslator ??= DefaultNameTranslator; + pgName ??= GetPgName(clrType, nameTranslator); + + Lock.EnterWriteLock(); + try + { + if (UserTypeMappings.TryRemove(pgName, out _)) + { + RecordChange(); + return true; + } + + return false; + } + finally + { + Lock.ExitWriteLock(); + } + } + + public override void AddTypeResolverFactory(TypeHandlerResolverFactory resolverFactory) + { + Lock.EnterWriteLock(); + try + { + // Since EFCore.PG plugins (and possibly other users) repeatedly call NpgsqlConnection.GlobalTypeMapped.UseNodaTime, + // we replace an existing resolver of the same CLR type. + var type = resolverFactory.GetType(); + + if (ResolverFactories[0].GetType() == type) + ResolverFactories[0] = resolverFactory; + else + { + for (var i = 0; i < ResolverFactories.Count; i++) + if (ResolverFactories[i].GetType() == type) + ResolverFactories.RemoveAt(i); + + ResolverFactories.Insert(0, resolverFactory); + } + + RecordChange(); + } + finally + { + Lock.ExitWriteLock(); + } + } + + public override void Reset() + { + Lock.EnterWriteLock(); + try + { + ResolverFactories.Clear(); + ResolverFactories.Add(new BuiltInTypeHandlerResolverFactory()); + + UserTypeMappings.Clear(); + + RecordChange(); + } + finally + { + Lock.ExitWriteLock(); + } + } + + internal void RecordChange() + { + _mappingsByClrType.Clear(); + Interlocked.Increment(ref _changeCounter); + } + + #endregion Mapping management + + #region NpgsqlDbType/DbType inference for NpgsqlParameter + + [RequiresUnreferencedCode("ToNpgsqlDbType uses interface-based reflection and isn't trimming-safe")] + internal bool TryResolveMappingByValue(object value, [NotNullWhen(true)] out TypeMappingInfo? typeMapping) + { + Lock.EnterReadLock(); + try + { + // We resolve as follows: + // 1. Cached by-type lookup (fast path). This will work for almost all types after the very first resolution. + // 2. Value-dependent type lookup (e.g. DateTime by Kind) via the resolvers. This includes complex types (e.g. array/range + // over DateTime), and the results cannot be cached. + // 3. Uncached by-type lookup (for the very first resolution of a given type) + + var type = value.GetType(); + if (_mappingsByClrType.TryGetValue(type, out typeMapping)) + return true; + + foreach (var resolverFactory in ResolverFactories) + if ((typeMapping = resolverFactory.GetMappingByValueDependentValue(value)) is not null) + return true; + + return TryResolveMappingByClrType(value.GetType(), out typeMapping); + } + finally + { + Lock.ExitReadLock(); + } + + bool TryResolveMappingByClrType(Type clrType, [NotNullWhen(true)] out TypeMappingInfo? typeMapping) + { + if (_mappingsByClrType.TryGetValue(clrType, out typeMapping)) + return true; + + foreach (var resolverFactory in ResolverFactories) + { + if ((typeMapping = resolverFactory.GetMappingByClrType(clrType)) is not null) + { + _mappingsByClrType[clrType] = typeMapping; + return true; + } + } + + if (clrType.IsArray) + { + if (TryResolveMappingByClrType(clrType.GetElementType()!, out var elementMapping)) + { + _mappingsByClrType[clrType] = typeMapping = new( + NpgsqlDbType.Array | elementMapping.NpgsqlDbType, + elementMapping.DataTypeName + "[]"); + return true; + } + + typeMapping = null; + return false; + } + + var typeInfo = clrType.GetTypeInfo(); + + var ilist = typeInfo.ImplementedInterfaces.FirstOrDefault(x => + x.GetTypeInfo().IsGenericType && x.GetGenericTypeDefinition() == typeof(IList<>)); + if (ilist != null) + { + if (TryResolveMappingByClrType(ilist.GetGenericArguments()[0], out var elementMapping)) + { + _mappingsByClrType[clrType] = typeMapping = new( + NpgsqlDbType.Array | elementMapping.NpgsqlDbType, + elementMapping.DataTypeName + "[]"); + return true; + } + + typeMapping = null; + return false; + } + + if (typeInfo.IsGenericType && clrType.GetGenericTypeDefinition() == typeof(NpgsqlRange<>)) + { + if (TryResolveMappingByClrType(clrType.GetGenericArguments()[0], out var elementMapping)) + { + _mappingsByClrType[clrType] = typeMapping = new( + NpgsqlDbType.Range | elementMapping.NpgsqlDbType, + dataTypeName: null); + return true; + } + + typeMapping = null; + return false; + } + + typeMapping = null; + return false; + } + } + + #endregion NpgsqlDbType/DbType inference for NpgsqlParameter + + #region Static translation tables + + public static string? NpgsqlDbTypeToDataTypeName(NpgsqlDbType npgsqlDbType) + => npgsqlDbType switch + { + // Numeric types + NpgsqlDbType.Smallint => "smallint", + NpgsqlDbType.Integer => "integer", + NpgsqlDbType.Bigint => "bigint", + NpgsqlDbType.Real => "real", + NpgsqlDbType.Double => "double precision", + NpgsqlDbType.Numeric => "numeric", + NpgsqlDbType.Money => "money", + + // Text types + NpgsqlDbType.Text => "text", + NpgsqlDbType.Xml => "xml", + NpgsqlDbType.Varchar => "character varying", + NpgsqlDbType.Char => "character", + NpgsqlDbType.Name => "name", + NpgsqlDbType.Refcursor => "refcursor", + NpgsqlDbType.Citext => "citext", + NpgsqlDbType.Jsonb => "jsonb", + NpgsqlDbType.Json => "json", + NpgsqlDbType.JsonPath => "jsonpath", + + // Date/time types + NpgsqlDbType.Timestamp => "timestamp without time zone", + NpgsqlDbType.TimestampTz => "timestamp with time zone", + NpgsqlDbType.Date => "date", + NpgsqlDbType.Time => "time without time zone", + NpgsqlDbType.TimeTz => "time with time zone", + NpgsqlDbType.Interval => "interval", + + // Network types + NpgsqlDbType.Cidr => "cidr", + NpgsqlDbType.Inet => "inet", + NpgsqlDbType.MacAddr => "macaddr", + NpgsqlDbType.MacAddr8 => "macaddr8", + + // Full-text search types + NpgsqlDbType.TsQuery => "tsquery", + NpgsqlDbType.TsVector => "tsvector", + + // Geometry types + NpgsqlDbType.Box => "box", + NpgsqlDbType.Circle => "circle", + NpgsqlDbType.Line => "line", + NpgsqlDbType.LSeg => "lseg", + NpgsqlDbType.Path => "path", + NpgsqlDbType.Point => "point", + NpgsqlDbType.Polygon => "polygon", + + // LTree types + NpgsqlDbType.LQuery => "lquery", + NpgsqlDbType.LTree => "ltree", + NpgsqlDbType.LTxtQuery => "ltxtquery", + + // UInt types + NpgsqlDbType.Oid => "oid", + NpgsqlDbType.Xid => "xid", + NpgsqlDbType.Xid8 => "xid8", + NpgsqlDbType.Cid => "cid", + NpgsqlDbType.Regtype => "regtype", + NpgsqlDbType.Regconfig => "regconfig", + + // Misc types + NpgsqlDbType.Boolean => "boolean", + NpgsqlDbType.Bytea => "bytea", + NpgsqlDbType.Uuid => "uuid", + NpgsqlDbType.Varbit => "bit varying", + NpgsqlDbType.Bit => "bit", + NpgsqlDbType.Hstore => "hstore", + + NpgsqlDbType.Geometry => "geometry", + NpgsqlDbType.Geography => "geography", + + // Built-in range types + NpgsqlDbType.IntegerRange => "int4range", + NpgsqlDbType.BigIntRange => "int8range", + NpgsqlDbType.NumericRange => "numrange", + NpgsqlDbType.TimestampRange => "tsrange", + NpgsqlDbType.TimestampTzRange => "tstzrange", + NpgsqlDbType.DateRange => "daterange", + + // Built-in multirange types + NpgsqlDbType.IntegerMultirange => "int4multirange", + NpgsqlDbType.BigIntMultirange => "int8multirange", + NpgsqlDbType.NumericMultirange => "nummultirange", + NpgsqlDbType.TimestampMultirange => "tsmultirange", + NpgsqlDbType.TimestampTzMultirange => "tstzmultirange", + NpgsqlDbType.DateMultirange => "datemultirange", + + // Internal types + NpgsqlDbType.Int2Vector => "int2vector", + NpgsqlDbType.Oidvector => "oidvector", + NpgsqlDbType.PgLsn => "pg_lsn", + NpgsqlDbType.Tid => "tid", + NpgsqlDbType.InternalChar => "char", + + // Special types + NpgsqlDbType.Unknown => "unknown", + + _ => npgsqlDbType.HasFlag(NpgsqlDbType.Array) + ? NpgsqlDbTypeToDataTypeName(npgsqlDbType & ~NpgsqlDbType.Array) + "[]" + : null // e.g. ranges + }; + + public static NpgsqlDbType DataTypeNameToNpgsqlDbType(string typeName) + { + // Strip any facet information (length/precision/scale) + var parenIndex = typeName.IndexOf('('); + if (parenIndex > -1) + typeName = typeName.Substring(0, parenIndex); + + return typeName switch + { + // Numeric types + "smallint" => NpgsqlDbType.Smallint, + "integer" or "int" => NpgsqlDbType.Integer, + "bigint" => NpgsqlDbType.Bigint, + "real" => NpgsqlDbType.Real, + "double precision" => NpgsqlDbType.Double, + "numeric" => NpgsqlDbType.Numeric, + "money" => NpgsqlDbType.Money, + + // Text types + "text" => NpgsqlDbType.Text, + "xml" => NpgsqlDbType.Xml, + "character varying" or "varchar" => NpgsqlDbType.Varchar, + "character" => NpgsqlDbType.Char, + "name" => NpgsqlDbType.Name, + "refcursor" => NpgsqlDbType.Refcursor, + "citext" => NpgsqlDbType.Citext, + "jsonb" => NpgsqlDbType.Jsonb, + "json" => NpgsqlDbType.Json, + "jsonpath" => NpgsqlDbType.JsonPath, + + // Date/time types + "timestamp without time zone" or "timestamp" => NpgsqlDbType.Timestamp, + "timestamp with time zone" or "timestamptz" => NpgsqlDbType.TimestampTz, + "date" => NpgsqlDbType.Date, + "time without time zone" or "timetz" => NpgsqlDbType.Time, + "time with time zone" or "time" => NpgsqlDbType.TimeTz, + "interval" => NpgsqlDbType.Interval, + + // Network types + "cidr" => NpgsqlDbType.Cidr, + "inet" => NpgsqlDbType.Inet, + "macaddr" => NpgsqlDbType.MacAddr, + "macaddr8" => NpgsqlDbType.MacAddr8, + + // Full-text search types + "tsquery" => NpgsqlDbType.TsQuery, + "tsvector" => NpgsqlDbType.TsVector, + + // Geometry types + "box" => NpgsqlDbType.Box, + "circle" => NpgsqlDbType.Circle, + "line" => NpgsqlDbType.Line, + "lseg" => NpgsqlDbType.LSeg, + "path" => NpgsqlDbType.Path, + "point" => NpgsqlDbType.Point, + "polygon" => NpgsqlDbType.Polygon, + + // LTree types + "lquery" => NpgsqlDbType.LQuery, + "ltree" => NpgsqlDbType.LTree, + "ltxtquery" => NpgsqlDbType.LTxtQuery, + + // UInt types + "oid" => NpgsqlDbType.Oid, + "xid" => NpgsqlDbType.Xid, + "xid8" => NpgsqlDbType.Xid8, + "cid" => NpgsqlDbType.Cid, + "regtype" => NpgsqlDbType.Regtype, + "regconfig" => NpgsqlDbType.Regconfig, + + // Misc types + "boolean" or "bool" => NpgsqlDbType.Boolean, + "bytea" => NpgsqlDbType.Bytea, + "uuid" => NpgsqlDbType.Uuid, + "bit varying" or "varbit" => NpgsqlDbType.Varbit, + "bit" => NpgsqlDbType.Bit, + "hstore" => NpgsqlDbType.Hstore, + + "geometry" => NpgsqlDbType.Geometry, + "geography" => NpgsqlDbType.Geography, + + // Built-in range types + "int4range" => NpgsqlDbType.IntegerRange, + "int8range" => NpgsqlDbType.BigIntRange, + "numrange" => NpgsqlDbType.NumericRange, + "tsrange" => NpgsqlDbType.TimestampRange, + "tstzrange" => NpgsqlDbType.TimestampTzRange, + "daterange" => NpgsqlDbType.DateRange, + + // Built-in multirange types + "int4multirange" => NpgsqlDbType.IntegerMultirange, + "int8multirange" => NpgsqlDbType.BigIntMultirange, + "nummultirange" => NpgsqlDbType.NumericMultirange, + "tsmultirange" => NpgsqlDbType.TimestampMultirange, + "tstzmultirange" => NpgsqlDbType.TimestampTzMultirange, + "datemultirange" => NpgsqlDbType.DateMultirange, + + // Internal types + "int2vector" => NpgsqlDbType.Int2Vector, + "oidvector" => NpgsqlDbType.Oidvector, + "pg_lsn" => NpgsqlDbType.PgLsn, + "tid" => NpgsqlDbType.Tid, + "char" => NpgsqlDbType.InternalChar, + + _ => typeName.EndsWith("[]", StringComparison.Ordinal) && + DataTypeNameToNpgsqlDbType(typeName.Substring(0, typeName.Length - 2)) is { } elementNpgsqlDbType && + elementNpgsqlDbType != NpgsqlDbType.Unknown + ? elementNpgsqlDbType | NpgsqlDbType.Array + : NpgsqlDbType.Unknown // e.g. ranges + }; + } + + internal static NpgsqlDbType? DbTypeToNpgsqlDbType(DbType dbType) + => dbType switch + { + DbType.AnsiString => NpgsqlDbType.Text, + DbType.Binary => NpgsqlDbType.Bytea, + DbType.Byte => NpgsqlDbType.Smallint, + DbType.Boolean => NpgsqlDbType.Boolean, + DbType.Currency => NpgsqlDbType.Money, + DbType.Date => NpgsqlDbType.Date, + DbType.DateTime => LegacyTimestampBehavior ? NpgsqlDbType.Timestamp : NpgsqlDbType.TimestampTz, + DbType.Decimal => NpgsqlDbType.Numeric, + DbType.VarNumeric => NpgsqlDbType.Numeric, + DbType.Double => NpgsqlDbType.Double, + DbType.Guid => NpgsqlDbType.Uuid, + DbType.Int16 => NpgsqlDbType.Smallint, + DbType.Int32 => NpgsqlDbType.Integer, + DbType.Int64 => NpgsqlDbType.Bigint, + DbType.Single => NpgsqlDbType.Real, + DbType.String => NpgsqlDbType.Text, + DbType.Time => NpgsqlDbType.Time, + DbType.AnsiStringFixedLength => NpgsqlDbType.Text, + DbType.StringFixedLength => NpgsqlDbType.Text, + DbType.Xml => NpgsqlDbType.Xml, + DbType.DateTime2 => NpgsqlDbType.Timestamp, + DbType.DateTimeOffset => NpgsqlDbType.TimestampTz, + + DbType.Object => null, + DbType.SByte => null, + DbType.UInt16 => null, + DbType.UInt32 => null, + DbType.UInt64 => null, + + _ => throw new ArgumentOutOfRangeException(nameof(dbType), dbType, null) + }; + + internal static DbType NpgsqlDbTypeToDbType(NpgsqlDbType npgsqlDbType) + => npgsqlDbType switch + { + // Numeric types + NpgsqlDbType.Smallint => DbType.Int16, + NpgsqlDbType.Integer => DbType.Int32, + NpgsqlDbType.Bigint => DbType.Int64, + NpgsqlDbType.Real => DbType.Single, + NpgsqlDbType.Double => DbType.Double, + NpgsqlDbType.Numeric => DbType.Decimal, + NpgsqlDbType.Money => DbType.Currency, + + // Text types + NpgsqlDbType.Text => DbType.String, + NpgsqlDbType.Xml => DbType.Xml, + NpgsqlDbType.Varchar => DbType.String, + NpgsqlDbType.Char => DbType.String, + NpgsqlDbType.Name => DbType.String, + NpgsqlDbType.Refcursor => DbType.String, + NpgsqlDbType.Citext => DbType.String, + NpgsqlDbType.Jsonb => DbType.Object, + NpgsqlDbType.Json => DbType.Object, + NpgsqlDbType.JsonPath => DbType.String, + + // Date/time types + NpgsqlDbType.Timestamp => LegacyTimestampBehavior ? DbType.DateTime : DbType.DateTime2, + NpgsqlDbType.TimestampTz => LegacyTimestampBehavior ? DbType.DateTimeOffset : DbType.DateTime, + NpgsqlDbType.Date => DbType.Date, + NpgsqlDbType.Time => DbType.Time, + + // Misc data types + NpgsqlDbType.Bytea => DbType.Binary, + NpgsqlDbType.Boolean => DbType.Boolean, + NpgsqlDbType.Uuid => DbType.Guid, + + NpgsqlDbType.Unknown => DbType.Object, + + _ => DbType.Object + }; + + #endregion Static translation tables +} diff --git a/LibExternal/Npgsql/TypeMapping/INpgsqlTypeMapper.cs b/LibExternal/Npgsql/TypeMapping/INpgsqlTypeMapper.cs new file mode 100644 index 0000000..48ccd16 --- /dev/null +++ b/LibExternal/Npgsql/TypeMapping/INpgsqlTypeMapper.cs @@ -0,0 +1,160 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Npgsql.Internal.TypeHandling; +using Npgsql.NameTranslation; +using NpgsqlTypes; + +// ReSharper disable UnusedMember.Global +namespace Npgsql.TypeMapping; + +/// +/// A type mapper, managing how to read and write CLR values to PostgreSQL data types. +/// A type mapper exists for each connection, as well as a single global type mapper +/// (accessible via ). +/// +/// +/// +public interface INpgsqlTypeMapper +{ + /// + /// The default name translator to convert CLR type names and member names. + /// + INpgsqlNameTranslator DefaultNameTranslator { get; } + + /// + /// Maps a CLR enum to a PostgreSQL enum type. + /// + /// + /// CLR enum labels are mapped by name to PostgreSQL enum labels. + /// The translation strategy can be controlled by the parameter, + /// which defaults to . + /// You can also use the on your enum fields to manually specify a PostgreSQL enum label. + /// If there is a discrepancy between the .NET and database labels while an enum is read or written, + /// an exception will be raised. + /// + /// + /// A PostgreSQL type name for the corresponding enum type in the database. + /// If null, the name translator given in will be used. + /// + /// + /// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class). + /// Defaults to + /// + /// The .NET enum type to be mapped + INpgsqlTypeMapper MapEnum( + string? pgName = null, + INpgsqlNameTranslator? nameTranslator = null) + where TEnum : struct, Enum; + + /// + /// Removes an existing enum mapping. + /// + /// + /// A PostgreSQL type name for the corresponding enum type in the database. + /// If null, the name translator given in will be used. + /// + /// + /// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class). + /// Defaults to + /// + bool UnmapEnum( + string? pgName = null, + INpgsqlNameTranslator? nameTranslator = null) + where TEnum : struct, Enum; + + /// + /// Maps a CLR type to a PostgreSQL composite type. + /// + /// + /// CLR fields and properties by string to PostgreSQL names. + /// The translation strategy can be controlled by the parameter, + /// which defaults to . + /// You can also use the on your members to manually specify a PostgreSQL name. + /// If there is a discrepancy between the .NET type and database type while a composite is read or written, + /// an exception will be raised. + /// + /// + /// A PostgreSQL type name for the corresponding composite type in the database. + /// If null, the name translator given in will be used. + /// + /// + /// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class). + /// Defaults to + /// + /// The .NET type to be mapped + [RequiresUnreferencedCode("Composite type mapping currently isn't trimming-safe.")] + INpgsqlTypeMapper MapComposite( + string? pgName = null, + INpgsqlNameTranslator? nameTranslator = null); + + /// + /// Removes an existing composite mapping. + /// + /// + /// A PostgreSQL type name for the corresponding composite type in the database. + /// If null, the name translator given in will be used. + /// + /// + /// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class). + /// Defaults to + /// + [RequiresUnreferencedCode("Composite type mapping currently isn't trimming-safe.")] + bool UnmapComposite( + string? pgName = null, + INpgsqlNameTranslator? nameTranslator = null); + + /// + /// Maps a CLR type to a composite type. + /// + /// + /// Maps CLR fields and properties by string to PostgreSQL names. + /// The translation strategy can be controlled by the parameter, + /// which defaults to . + /// If there is a discrepancy between the .NET type and database type while a composite is read or written, + /// an exception will be raised. + /// + /// The .NET type to be mapped. + /// + /// A PostgreSQL type name for the corresponding composite type in the database. + /// If null, the name translator given in will be used. + /// + /// + /// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class). + /// Defaults to + /// + [RequiresUnreferencedCode("Composite type mapping currently isn't trimming-safe.")] + INpgsqlTypeMapper MapComposite( + Type clrType, + string? pgName = null, + INpgsqlNameTranslator? nameTranslator = null); + + /// + /// Removes an existing composite mapping. + /// + /// The .NET type to be unmapped. + /// + /// A PostgreSQL type name for the corresponding composite type in the database. + /// If null, the name translator given in will be used. + /// + /// + /// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class). + /// Defaults to + /// + [RequiresUnreferencedCode("Composite type mapping currently isn't trimming-safe.")] + bool UnmapComposite( + Type clrType, + string? pgName = null, + INpgsqlNameTranslator? nameTranslator = null); + + /// + /// Adds a type resolver factory, which produces resolvers that can add or modify support for PostgreSQL types. + /// Typically used by plugins. + /// + /// The type resolver factory to be added. + void AddTypeResolverFactory(TypeHandlerResolverFactory resolverFactory); + + /// + /// Resets all mapping changes performed on this type mapper and reverts it to its original, starting state. + /// + void Reset(); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/TypeMapping/PostgresTypeOIDs.cs b/LibExternal/Npgsql/TypeMapping/PostgresTypeOIDs.cs new file mode 100644 index 0000000..f803dca --- /dev/null +++ b/LibExternal/Npgsql/TypeMapping/PostgresTypeOIDs.cs @@ -0,0 +1,112 @@ +#pragma warning disable RS0016 +#pragma warning disable 1591 + +namespace Npgsql.TypeMapping; + +/// +/// Holds well-known, built-in PostgreSQL type OIDs. +/// +/// +/// Source: +/// +public static class PostgresTypeOIDs +{ + // Numeric + public const uint Int8 = 20; + public const uint Float8 = 701; + public const uint Int4 = 23; + public const uint Numeric = 1700; + public const uint Float4 = 700; + public const uint Int2 = 21; + public const uint Money = 790; + + // Boolean + public const uint Bool = 16; + + // Geometric + public const uint Box = 603; + public const uint Circle = 718; + public const uint Line = 628; + public const uint LSeg = 601; + public const uint Path = 602; + public const uint Point = 600; + public const uint Polygon = 604; + + // Character + public const uint BPChar = 1042; + public const uint Text = 25; + public const uint Varchar = 1043; + public const uint Name = 19; + public const uint Char = 18; + + // Binary data + public const uint Bytea = 17; + + // Date/Time + public const uint Date = 1082; + public const uint Time = 1083; + public const uint Timestamp = 1114; + public const uint TimestampTz = 1184; + public const uint Interval = 1186; + public const uint TimeTz = 1266; + public const uint Abstime = 702; + + // Network address + public const uint Inet = 869; + public const uint Cidr = 650; + public const uint Macaddr = 829; + public const uint Macaddr8 = 774; + + // Bit string + public const uint Bit = 1560; + public const uint Varbit = 1562; + + // Text search + public const uint TsVector = 3614; + public const uint TsQuery = 3615; + public const uint Regconfig = 3734; + + // UUID + public const uint Uuid = 2950; + + // XML + public const uint Xml = 142; + + // JSON + public const uint Json = 114; + public const uint Jsonb = 3802; + public const uint JsonPath = 4072; + + // public + public const uint Refcursor = 1790; + public const uint Oidvector = 30; + public const uint Int2vector = 22; + public const uint Oid = 26; + public const uint Xid = 28; + public const uint Xid8 = 5069; + public const uint Cid = 29; + public const uint Regtype = 2206; + public const uint Tid = 27; + public const uint PgLsn = 3220; + + // Special + public const uint Record = 2249; + public const uint Void = 2278; + public const uint Unknown = 705; + + // Range types + public const uint Int4Range = 3904; + public const uint Int8Range = 3926; + public const uint NumRange = 3906; + public const uint TsRange = 3908; + public const uint TsTzRange = 3910; + public const uint DateRange = 3912; + + // Multirange types + public const uint Int4Multirange = 4451; + public const uint Int8Multirange = 4536; + public const uint NumMultirange = 4532; + public const uint TsMultirange = 4533; + public const uint TsTzMultirange = 4534; + public const uint DateMultirange = 4535; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/TypeMapping/TypeMapperBase.cs b/LibExternal/Npgsql/TypeMapping/TypeMapperBase.cs new file mode 100644 index 0000000..987ed40 --- /dev/null +++ b/LibExternal/Npgsql/TypeMapping/TypeMapperBase.cs @@ -0,0 +1,59 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using Npgsql.Internal.TypeHandling; +using NpgsqlTypes; + +namespace Npgsql.TypeMapping; + +abstract class TypeMapperBase : INpgsqlTypeMapper +{ + public INpgsqlNameTranslator DefaultNameTranslator { get; } + + protected TypeMapperBase(INpgsqlNameTranslator defaultNameTranslator) + { + if (defaultNameTranslator == null) + throw new ArgumentNullException(nameof(defaultNameTranslator)); + + DefaultNameTranslator = defaultNameTranslator; + } + + #region Mapping management + + /// + public abstract INpgsqlTypeMapper MapEnum(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) + where TEnum : struct, Enum; + + /// + public abstract bool UnmapEnum(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) + where TEnum : struct, Enum; + + /// + [RequiresUnreferencedCode("Composite type mapping currently isn't trimming-safe.")] + public abstract INpgsqlTypeMapper MapComposite(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null); + + /// + [RequiresUnreferencedCode("Composite type mapping currently isn't trimming-safe.")] + public abstract INpgsqlTypeMapper MapComposite(Type clrType, string? pgName = null, INpgsqlNameTranslator? nameTranslator = null); + + /// + public abstract bool UnmapComposite(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null); + + /// + public abstract bool UnmapComposite(Type clrType, string? pgName = null, INpgsqlNameTranslator? nameTranslator = null); + + /// + public abstract void AddTypeResolverFactory(TypeHandlerResolverFactory resolverFactory); + + public abstract void Reset(); + + #endregion Composite mapping + + #region Misc + + private protected static string GetPgName(Type clrType, INpgsqlNameTranslator nameTranslator) + => clrType.GetCustomAttribute()?.PgName + ?? nameTranslator.TranslateTypeName(clrType.Name); + + #endregion Misc +} \ No newline at end of file diff --git a/LibExternal/Npgsql/UnpooledConnectorSource.cs b/LibExternal/Npgsql/UnpooledConnectorSource.cs new file mode 100644 index 0000000..88bcb87 --- /dev/null +++ b/LibExternal/Npgsql/UnpooledConnectorSource.cs @@ -0,0 +1,49 @@ +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using System.Transactions; +using Npgsql.Internal; +using Npgsql.Util; + +namespace Npgsql; + +sealed class UnpooledConnectorSource : ConnectorSource +{ + public UnpooledConnectorSource(NpgsqlConnectionStringBuilder settings, string connString) + : base(settings, connString) + { + } + + volatile int _numConnectors; + + internal override (int Total, int Idle, int Busy) Statistics => (_numConnectors, 0, _numConnectors); + + internal override bool OwnsConnectors => true; + + internal override async ValueTask Get( + NpgsqlConnection conn, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken) + { + var connector = new NpgsqlConnector(this, conn); + await connector.Open(timeout, async, cancellationToken); + Interlocked.Increment(ref _numConnectors); + return connector; + } + + internal override bool TryGetIdleConnector([NotNullWhen(true)] out NpgsqlConnector? connector) + { + connector = null; + return false; + } + + internal override ValueTask OpenNewConnector( + NpgsqlConnection conn, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken) + => new((NpgsqlConnector?)null); + + internal override void Return(NpgsqlConnector connector) + { + Interlocked.Decrement(ref _numConnectors); + connector.Close(); + } + + internal override void Clear() {} +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Util/ManualResetValueTaskSource.cs b/LibExternal/Npgsql/Util/ManualResetValueTaskSource.cs new file mode 100644 index 0000000..55e45aa --- /dev/null +++ b/LibExternal/Npgsql/Util/ManualResetValueTaskSource.cs @@ -0,0 +1,21 @@ +using System; +using System.Threading.Tasks.Sources; + +namespace Npgsql.Util; + +sealed class ManualResetValueTaskSource : IValueTaskSource, IValueTaskSource +{ + ManualResetValueTaskSourceCore _core; // mutable struct; do not make this readonly + + public bool RunContinuationsAsynchronously { get => _core.RunContinuationsAsynchronously; set => _core.RunContinuationsAsynchronously = value; } + public short Version => _core.Version; + public void Reset() => _core.Reset(); + public void SetResult(T result) => _core.SetResult(result); + public void SetException(Exception error) => _core.SetException(error); + + public T GetResult(short token) => _core.GetResult(token); + void IValueTaskSource.GetResult(short token) => _core.GetResult(token); + public ValueTaskSourceStatus GetStatus(short token) => _core.GetStatus(token); + public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) + => _core.OnCompleted(continuation, state, token, flags); +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Util/PGUtil.cs b/LibExternal/Npgsql/Util/PGUtil.cs new file mode 100644 index 0000000..7f91041 --- /dev/null +++ b/LibExternal/Npgsql/Util/PGUtil.cs @@ -0,0 +1,219 @@ +using Npgsql.Internal; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Npgsql.Util; + +static class Statics +{ +#if DEBUG + internal static bool LegacyTimestampBehavior; + internal static bool DisableDateTimeInfinityConversions; +#else + internal static readonly bool LegacyTimestampBehavior; + internal static readonly bool DisableDateTimeInfinityConversions; +#endif + + static Statics() + { + LegacyTimestampBehavior = AppContext.TryGetSwitch("Npgsql.EnableLegacyTimestampBehavior", out var enabled) && enabled; + DisableDateTimeInfinityConversions = AppContext.TryGetSwitch("Npgsql.DisableDateTimeInfinityConversions", out enabled) && enabled; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static T Expect(IBackendMessage msg, NpgsqlConnector connector) + { + if (msg is T asT) + return asT; + + throw connector.Break( + new NpgsqlException($"Received backend message {msg.Code} while expecting {typeof(T).Name}. " + + "Please file a bug.")); + } + + internal static DeferDisposable Defer(Action action) => new(action); + internal static DeferDisposable Defer(Action action, T arg) => new(action, arg); + internal static DeferDisposable Defer(Action action, T1 arg1, T2 arg2) => new(action, arg1, arg2); + // internal static AsyncDeferDisposable DeferAsync(Func func) => new AsyncDeferDisposable(func); + internal static AsyncDeferDisposable DeferAsync(Func func) => new(func); + + internal readonly struct DeferDisposable : IDisposable + { + readonly Action _action; + public DeferDisposable(Action action) => _action = action; + public void Dispose() => _action(); + } + + internal readonly struct DeferDisposable : IDisposable + { + readonly Action _action; + readonly T _arg; + public DeferDisposable(Action action, T arg) + { + _action = action; + _arg = arg; + } + public void Dispose() => _action(_arg); + } + + internal readonly struct DeferDisposable : IDisposable + { + readonly Action _action; + readonly T1 _arg1; + readonly T2 _arg2; + public DeferDisposable(Action action, T1 arg1, T2 arg2) + { + _action = action; + _arg1 = arg1; + _arg2 = arg2; + } + public void Dispose() => _action(_arg1, _arg2); + } + + internal readonly struct AsyncDeferDisposable : IAsyncDisposable + { + readonly Func _func; + public AsyncDeferDisposable(Func func) => _func = func; + public async ValueTask DisposeAsync() => await _func(); + } +} + +// ReSharper disable once InconsistentNaming +static class PGUtil +{ + internal static readonly UTF8Encoding UTF8Encoding = new(false, true); + internal static readonly UTF8Encoding RelaxedUTF8Encoding = new(false, false); + + internal const int BitsInInt = sizeof(int) * 8; + + internal static void ValidateBackendMessageCode(BackendMessageCode code) + { + switch (code) + { + case BackendMessageCode.AuthenticationRequest: + case BackendMessageCode.BackendKeyData: + case BackendMessageCode.BindComplete: + case BackendMessageCode.CloseComplete: + case BackendMessageCode.CommandComplete: + case BackendMessageCode.CopyData: + case BackendMessageCode.CopyDone: + case BackendMessageCode.CopyBothResponse: + case BackendMessageCode.CopyInResponse: + case BackendMessageCode.CopyOutResponse: + case BackendMessageCode.DataRow: + case BackendMessageCode.EmptyQueryResponse: + case BackendMessageCode.ErrorResponse: + case BackendMessageCode.FunctionCall: + case BackendMessageCode.FunctionCallResponse: + case BackendMessageCode.NoData: + case BackendMessageCode.NoticeResponse: + case BackendMessageCode.NotificationResponse: + case BackendMessageCode.ParameterDescription: + case BackendMessageCode.ParameterStatus: + case BackendMessageCode.ParseComplete: + case BackendMessageCode.PasswordPacket: + case BackendMessageCode.PortalSuspended: + case BackendMessageCode.ReadyForQuery: + case BackendMessageCode.RowDescription: + return; + default: + throw new NpgsqlException("Unknown message code: " + code); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int RotateShift(int val, int shift) + => (val << shift) | (val >> (BitsInInt - shift)); + + internal static readonly Task TrueTask = Task.FromResult(true); + internal static readonly Task FalseTask = Task.FromResult(false); +} + +enum FormatCode : short +{ + Text = 0, + Binary = 1 +} + +static class EnumerableExtensions +{ + internal static string Join(this IEnumerable values, string separator) + { + return string.Join(separator, values); + } +} + +static class ExceptionExtensions +{ + internal static Exception UnwrapAggregate(this Exception exception) + => exception is AggregateException agg ? agg.InnerException! : exception; +} + +/// +/// Represents a timeout that will expire at some point. +/// +public readonly struct NpgsqlTimeout +{ + readonly DateTime _expiration; + + internal static NpgsqlTimeout Infinite = new(TimeSpan.Zero); + + internal NpgsqlTimeout(TimeSpan expiration) + => _expiration = expiration > TimeSpan.Zero + ? DateTime.UtcNow + expiration + : expiration == TimeSpan.Zero + ? DateTime.MaxValue + : DateTime.MinValue; + + internal void Check() + { + if (HasExpired) + throw new TimeoutException(); + } + + internal void CheckAndApply(NpgsqlConnector connector) + { + if (!IsSet) + return; + + var timeLeft = CheckAndGetTimeLeft(); + // Set the remaining timeout on the read and write buffers + connector.ReadBuffer.Timeout = connector.WriteBuffer.Timeout = timeLeft; + + // Note that we set UserTimeout as well, otherwise the read timeout will get overwritten in ReadMessage + // Note also that we must set the read buffer's timeout directly (above), since the SSL handshake + // reads data directly from the buffer, without going through ReadMessage. + connector.UserTimeout = (int) Math.Ceiling(timeLeft.TotalMilliseconds); + } + + internal bool IsSet => _expiration != DateTime.MaxValue; + + internal bool HasExpired => DateTime.UtcNow >= _expiration; + + internal TimeSpan CheckAndGetTimeLeft() + { + if (!IsSet) + return Timeout.InfiniteTimeSpan; + var timeLeft = _expiration - DateTime.UtcNow; + if (timeLeft <= TimeSpan.Zero) + Check(); + return timeLeft; + } +} + +static class MethodInfos +{ + internal static readonly ConstructorInfo InvalidCastExceptionCtor = + typeof(InvalidCastException).GetConstructor(new[] { typeof(string) })!; + + internal static readonly MethodInfo StringFormat = + typeof(string).GetMethod(nameof(string.Format), new[] { typeof(string), typeof(object) })!; + + internal static readonly MethodInfo ObjectGetType = + typeof(object).GetMethod(nameof(GetType), new Type[0])!; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Util/ResettableCancellationTokenSource.cs b/LibExternal/Npgsql/Util/ResettableCancellationTokenSource.cs new file mode 100644 index 0000000..5ac5994 --- /dev/null +++ b/LibExternal/Npgsql/Util/ResettableCancellationTokenSource.cs @@ -0,0 +1,233 @@ +using System; +using System.Diagnostics; +using System.Threading; +using static System.Threading.Timeout; + +namespace Npgsql.Util; + +/// +/// A wrapper around to simplify reset management. +/// +/// +/// Since there's no way to reset a once it was cancelled, +/// we need to make sure that an existing cancellation token source hasn't been cancelled, +/// every time we start it (see https://github.com/dotnet/runtime/issues/4694). +/// +class ResettableCancellationTokenSource : IDisposable +{ + bool isDisposed; + + public TimeSpan Timeout { get; set; } + + volatile CancellationTokenSource _cts = new(); + CancellationTokenRegistration _registration; + + /// + /// Used, so we wouldn't concurently use the cts for the cancellation, while it's being disposed + /// + readonly object lockObject = new(); + +#if DEBUG + bool _isRunning; +#endif + + public ResettableCancellationTokenSource() => Timeout = InfiniteTimeSpan; + + public ResettableCancellationTokenSource(TimeSpan timeout) => Timeout = timeout; + + /// + /// Set the timeout on the wrapped + /// and make sure that it hasn't been cancelled yet + /// + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// The from the wrapped + public CancellationToken Start(CancellationToken cancellationToken = default) + { +#if DEBUG + Debug.Assert(!_isRunning); +#endif + lock (lockObject) + { + // if there was an attempt to cancel while the connector was breaking + // we do nothing and return the default token + // as we're going to fail while reading or writing anyway + if (isDisposed) + { +#if DEBUG + _isRunning = true; +#endif + return CancellationToken.None; + } + + _cts.CancelAfter(Timeout); + if (_cts.IsCancellationRequested) + { + _cts.Dispose(); + _cts = new CancellationTokenSource(Timeout); + } + } + if (cancellationToken.CanBeCanceled) + _registration = cancellationToken.Register(cts => ((CancellationTokenSource)cts!).Cancel(), _cts); +#if DEBUG + _isRunning = true; +#endif + return _cts.Token; + } + + /// + /// Restart the timeout on the wrapped without reinitializing it, + /// even if is already set to + /// + public void RestartTimeoutWithoutReset() + { + lock (lockObject) + { + // if there was an attempt to cancel while the connector was breaking + // we do nothing and return the default token + // as we're going to fail while reading or writing anyway + if (!isDisposed) + _cts.CancelAfter(Timeout); + } + } + + /// + /// Reset the wrapper to contain a unstarted and uncancelled + /// in order make sure the next call to will not invalidate + /// the cancellation token. + /// + /// + /// An optional token to cancel the asynchronous operation. The default value is . + /// + /// The from the wrapped + public CancellationToken Reset(CancellationToken cancellationToken = default) + { + _registration.Dispose(); + lock (lockObject) + { + // if there was an attempt to cancel while the connector was breaking + // we do nothing and return + // as we're going to fail while reading or writing anyway + if (isDisposed) + { +#if DEBUG + _isRunning = false; +#endif + return CancellationToken.None; + } + + _cts.CancelAfter(InfiniteTimeSpan); + if (_cts.IsCancellationRequested) + { + _cts.Dispose(); + _cts = new CancellationTokenSource(); + } + } + if (cancellationToken.CanBeCanceled) + _registration = cancellationToken.Register(cts => ((CancellationTokenSource)cts!).Cancel(), _cts); +#if DEBUG + _isRunning = false; +#endif + return _cts.Token; + } + + /// + /// Reset the wrapper to contain a unstarted and uncancelled + /// in order make sure the next call to will not invalidate + /// the cancellation token. + /// + public void ResetCts() + { + if (_cts.IsCancellationRequested) + { + _cts.Dispose(); + _cts = new CancellationTokenSource(); + } + } + + /// + /// Set the timeout on the wrapped + /// to + /// + /// + /// can still arrive at a state + /// where it's value is if the + /// passed to gets a cancellation request. + /// If this is the case it will be resolved upon the next call to + /// or . Calling multiple times or without calling + /// first will do no any harm (besides eating a tiny amount of CPU cycles). + /// + public void Stop() + { + _registration.Dispose(); + lock (lockObject) + { + // if there was an attempt to cancel while the connector was breaking + // we do nothing + if (!isDisposed) + _cts.CancelAfter(InfiniteTimeSpan); + } +#if DEBUG + _isRunning = false; +#endif + } + + /// + /// Cancel the wrapped + /// + public void Cancel() + { + lock (lockObject) + { + // if there was an attempt to cancel while the connector was breaking + // we do nothing + if (isDisposed) + return; + _cts.Cancel(); + } + } + + /// + /// Cancel the wrapped after delay + /// + public void CancelAfter(int delay) + { + lock (lockObject) + { + // if there was an attempt to cancel while the connector was breaking + // we do nothing + if (isDisposed) + return; + _cts.CancelAfter(delay); + } + } + + /// + /// The from the wrapped + /// . + /// + /// + /// The token is only valid after calling + /// and before calling the next time. + /// Otherwise you may end up with a token that has already been + /// cancelled or belongs to a cancellation token source that has + /// been disposed. + /// + public CancellationToken Token => _cts.Token; + + public bool IsCancellationRequested => _cts.IsCancellationRequested; + + public void Dispose() + { + Debug.Assert(!isDisposed); + + lock (lockObject) + { + _registration.Dispose(); + _cts.Dispose(); + + isDisposed = true; + } + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/Util/VersionExtensions.cs b/LibExternal/Npgsql/Util/VersionExtensions.cs new file mode 100644 index 0000000..4501dd7 --- /dev/null +++ b/LibExternal/Npgsql/Util/VersionExtensions.cs @@ -0,0 +1,14 @@ +using System; + +namespace Npgsql.Util; + +static class VersionExtensions +{ + /// + /// Allocation free helper function to find if version is greater than expected + /// + public static bool IsGreaterOrEqual(this Version version, int major, int minor = 0) + => version.Major != major + ? version.Major > major + : version.Minor >= minor; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/VolatileResourceManager.cs b/LibExternal/Npgsql/VolatileResourceManager.cs new file mode 100644 index 0000000..16a5245 --- /dev/null +++ b/LibExternal/Npgsql/VolatileResourceManager.cs @@ -0,0 +1,296 @@ +using System; +using System.Diagnostics; +using System.Threading; +using System.Transactions; +using Npgsql.Internal; +using Npgsql.Logging; + +namespace Npgsql; + +/// +/// +/// +/// +/// Note that a connection may be closed before its TransactionScope completes. In this case we close the NpgsqlConnection +/// as usual but the connector in a special list in the pool; it will be closed only when the scope completes. +/// +class VolatileResourceManager : ISinglePhaseNotification +{ + NpgsqlConnector _connector; + Transaction _transaction; + readonly string _txId; + NpgsqlTransaction _localTx = null!; + string? _preparedTxName; + bool IsPrepared => _preparedTxName != null; + bool _isDisposed; + + static readonly NpgsqlLogger Log = NpgsqlLogManager.CreateLogger(nameof(VolatileResourceManager)); + + const int MaximumRollbackAttempts = 20; + + internal VolatileResourceManager(NpgsqlConnection connection, Transaction transaction) + { + _connector = connection.Connector!; + _transaction = transaction; + // _tx gets disposed by System.Transactions at some point, but we want to be able to log its local ID + _txId = transaction.TransactionInformation.LocalIdentifier; + } + + internal void Init() + => _localTx = _connector.Connection!.BeginTransaction(ConvertIsolationLevel(_transaction.IsolationLevel)); + + public void SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment) + { + CheckDisposed(); + Log.Debug($"Single Phase Commit (localid={_txId})", _connector.Id); + + try + { + _localTx.Commit(); + singlePhaseEnlistment.Committed(); + } + catch (PostgresException e) + { + singlePhaseEnlistment.Aborted(e); + } + catch (Exception e) + { + singlePhaseEnlistment.InDoubt(e); + } + finally + { + Dispose(); + } + } + + public void Prepare(PreparingEnlistment preparingEnlistment) + { + CheckDisposed(); + Log.Debug($"Two-phase transaction prepare (localid={_txId})", _connector.Id); + + // The PostgreSQL prepared transaction name is the distributed GUID + our connection's process ID, for uniqueness + _preparedTxName = $"{_transaction.TransactionInformation.DistributedIdentifier}/{_connector.BackendProcessId}"; + + try + { + using (_connector.StartUserAction()) + _connector.ExecuteInternalCommand($"PREPARE TRANSACTION '{_preparedTxName}'"); + + // The MSDTC, which manages escalated distributed transactions, performs the 2nd phase + // asynchronously - this means that TransactionScope.Dispose() will return before all + // resource managers have actually commit. + // If the same connection tries to enlist to a new TransactionScope immediately after + // disposing an old TransactionScope, its EnlistedTransaction must have been cleared + // (or we'll throw a double enlistment exception). This must be done here at the 1st phase + // (which is sync). + if (_connector.Connection != null) + _connector.Connection.EnlistedTransaction = null; + + preparingEnlistment.Prepared(); + } + catch (Exception e) + { + Dispose(); + preparingEnlistment.ForceRollback(e); + } + } + + public void Commit(Enlistment enlistment) + { + CheckDisposed(); + Log.Debug($"Two-phase transaction commit (localid={_txId})", _connector.Id); + + try + { + if (_connector.Connection == null) + { + // The connection has been closed before the TransactionScope was disposed. + // The connector is unbound from its connection and is sitting in the pool's + // pending enlisted connector list. Since there's no risk of the connector being + // used by anyone we can executed the 2nd phase on it directly (see below). + using (_connector.StartUserAction()) + _connector.ExecuteInternalCommand($"COMMIT PREPARED '{_preparedTxName}'"); + } + else + { + // The connection is still open and potentially will be reused by by the user. + // The MSDTC, which manages escalated distributed transactions, performs the 2nd phase + // asynchronously - this means that TransactionScope.Dispose() will return before all + // resource managers have actually commit. This can cause a concurrent connection use scenario + // if the user continues to use their connection after disposing the scope, and the MSDTC + // requests a commit at that exact time. + // To avoid this, we open a new connection for performing the 2nd phase. + using var conn2 = (NpgsqlConnection)((ICloneable)_connector.Connection).Clone(); + conn2.Open(); + + var connector = conn2.Connector!; + using (connector.StartUserAction()) + connector.ExecuteInternalCommand($"COMMIT PREPARED '{_preparedTxName}'"); + } + } + catch (Exception e) + { + Log.Error("Exception during two-phase transaction commit (localid={TransactionId})", e, _connector.Id); + } + finally + { + Dispose(); + enlistment.Done(); + } + } + + public void Rollback(Enlistment enlistment) + { + CheckDisposed(); + + try + { + if (IsPrepared) + RollbackTwoPhase(); + else + RollbackLocal(); + } + catch (Exception e) + { + Log.Error($"Exception during transaction rollback (localid={_txId})", e, _connector.Id); + } + finally + { + Dispose(); + enlistment.Done(); + } + } + + public void InDoubt(Enlistment enlistment) + { + Log.Warn($"Two-phase transaction in doubt (localid={_txId})", _connector.Id); + + // TODO: Is this the correct behavior? + try + { + RollbackTwoPhase(); + } + catch (Exception e) + { + Log.Error($"Exception during transaction rollback (localid={_txId})", e, _connector.Id); + } + finally + { + Dispose(); + enlistment.Done(); + } + } + + void RollbackLocal() + { + Log.Debug($"Single-phase transaction rollback (localid={_txId})", _connector.Id); + + var attempt = 0; + while (true) + { + try + { + _localTx.Rollback(); + return; + } + catch (NpgsqlOperationInProgressException) + { + // Repeatedly attempts to rollback, to support timeout-triggered rollbacks that occur + // while the connection is busy. + + // This really shouldn't be necessary, but just in case + if (attempt++ == MaximumRollbackAttempts) + throw new Exception($"Could not roll back after {MaximumRollbackAttempts} attempts, aborting. Transaction is in an unknown state."); + + Log.Warn($"Connection in use while trying to rollback, will cancel and retry (localid={_txId}", _connector.Id); + _connector.PerformPostgresCancellation(); + // Cancellations are asynchronous, give it some time + Thread.Sleep(500); + } + } + } + + void RollbackTwoPhase() + { + // This only occurs if we've started a two-phase commit but one of the commits has failed. + Log.Debug($"Two-phase transaction rollback (localid={_txId})", _connector.Id); + + if (_connector.Connection == null) + { + // The connection has been closed before the TransactionScope was disposed. + // The connector is unbound from its connection and is sitting in the pool's + // pending enlisted connector list. Since there's no risk of the connector being + // used by anyone we can executed the 2nd phase on it directly (see below). + using (_connector.StartUserAction()) + _connector.ExecuteInternalCommand($"ROLLBACK PREPARED '{_preparedTxName}'"); + } + else + { + // The connection is still open and potentially will be reused by by the user. + // The MSDTC, which manages escalated distributed transactions, performs the 2nd phase + // asynchronously - this means that TransactionScope.Dispose() will return before all + // resource managers have actually commit. This can cause a concurrent connection use scenario + // if the user continues to use their connection after disposing the scope, and the MSDTC + // requests a commit at that exact time. + // To avoid this, we open a new connection for performing the 2nd phase. + using var conn2 = (NpgsqlConnection)((ICloneable)_connector.Connection).Clone(); + conn2.Open(); + + var connector = conn2.Connector!; + using (connector.StartUserAction()) + connector.ExecuteInternalCommand($"ROLLBACK PREPARED '{_preparedTxName}'"); + } + } + + #region Dispose/Cleanup + +#pragma warning disable CS8625 + void Dispose() + { + if (_isDisposed) + return; + + Log.Trace($"Cleaning up resource manager (localid={_txId}", _connector.Id); + if (_localTx != null) + { + _localTx.Dispose(); + _localTx = null; + } + + if (_connector.Connection != null) + _connector.Connection.EnlistedTransaction = null; + else + { + // We're here for connections which were closed before their TransactionScope completes. + // These need to be closed now. + // We should return the connector to the pool only if we've successfully removed it from the pending list + if (_connector.TryRemovePendingEnlistedConnector(_transaction)) + _connector.Return(); + } + + _connector = null!; + _transaction = null!; + _isDisposed = true; + } +#pragma warning restore CS8625 + + void CheckDisposed() + { + if (_isDisposed) + throw new ObjectDisposedException(nameof(VolatileResourceManager)); + } + + #endregion + + static System.Data.IsolationLevel ConvertIsolationLevel(IsolationLevel isolationLevel) + => isolationLevel switch + { + IsolationLevel.Chaos => System.Data.IsolationLevel.Chaos, + IsolationLevel.ReadCommitted => System.Data.IsolationLevel.ReadCommitted, + IsolationLevel.ReadUncommitted => System.Data.IsolationLevel.ReadUncommitted, + IsolationLevel.RepeatableRead => System.Data.IsolationLevel.RepeatableRead, + IsolationLevel.Serializable => System.Data.IsolationLevel.Serializable, + IsolationLevel.Snapshot => System.Data.IsolationLevel.Snapshot, + _ => System.Data.IsolationLevel.Unspecified + }; +} \ No newline at end of file diff --git a/LibExternal/Npgsql/bin/Debug/net8.0/Npgsql.deps.json b/LibExternal/Npgsql/bin/Debug/net8.0/Npgsql.deps.json new file mode 100644 index 0000000..64c091f --- /dev/null +++ b/LibExternal/Npgsql/bin/Debug/net8.0/Npgsql.deps.json @@ -0,0 +1,23 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v8.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v8.0": { + "Npgsql/1.0.0": { + "runtime": { + "Npgsql.dll": {} + } + } + } + }, + "libraries": { + "Npgsql/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/LibExternal/Npgsql/bin/Debug/net8.0/Npgsql.dll b/LibExternal/Npgsql/bin/Debug/net8.0/Npgsql.dll new file mode 100644 index 0000000..dab0131 Binary files /dev/null and b/LibExternal/Npgsql/bin/Debug/net8.0/Npgsql.dll differ diff --git a/LibExternal/Npgsql/bin/Debug/net8.0/Npgsql.pdb b/LibExternal/Npgsql/bin/Debug/net8.0/Npgsql.pdb new file mode 100644 index 0000000..4ab92eb Binary files /dev/null and b/LibExternal/Npgsql/bin/Debug/net8.0/Npgsql.pdb differ diff --git a/LibExternal/Npgsql/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs b/LibExternal/Npgsql/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs new file mode 100644 index 0000000..2217181 --- /dev/null +++ b/LibExternal/Npgsql/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")] diff --git a/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.AssemblyInfo.cs b/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.AssemblyInfo.cs new file mode 100644 index 0000000..fbba482 --- /dev/null +++ b/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.AssemblyInfo.cs @@ -0,0 +1,23 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("Npgsql")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+37e709e30d2205dd559831f65211b8dd5d40a303")] +[assembly: System.Reflection.AssemblyProductAttribute("Npgsql")] +[assembly: System.Reflection.AssemblyTitleAttribute("Npgsql")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.AssemblyInfoInputs.cache b/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.AssemblyInfoInputs.cache new file mode 100644 index 0000000..94d1d73 --- /dev/null +++ b/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +d62baadb73b9aeacf6269b976caaf5dba496e8fc9eb8b9fd343edf512ce48d27 diff --git a/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.GeneratedMSBuildEditorConfig.editorconfig b/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..78b0e62 --- /dev/null +++ b/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,15 @@ +is_global = true +build_property.TargetFramework = net8.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = Npgsql +build_property.ProjectDir = C:\KIT\Kit.Core\LibExternal\Npgsql\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 8.0 +build_property.EnableCodeStyleSeverity = diff --git a/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.GlobalUsings.g.cs b/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.GlobalUsings.g.cs new file mode 100644 index 0000000..8578f3d --- /dev/null +++ b/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.GlobalUsings.g.cs @@ -0,0 +1,8 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.assets.cache b/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.assets.cache new file mode 100644 index 0000000..cb2e0ab Binary files /dev/null and b/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.assets.cache differ diff --git a/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.csproj.CoreCompileInputs.cache b/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..8998f47 --- /dev/null +++ b/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +ecb11254eecb822dec48c6d2e6c9ddc6922d4c8594b457dee940ed700ea7066c diff --git a/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.csproj.FileListAbsolute.txt b/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..0672b42 --- /dev/null +++ b/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.csproj.FileListAbsolute.txt @@ -0,0 +1,11 @@ +C:\KIT\Kit.Core\LibExternal\Npgsql\bin\Debug\net8.0\Npgsql.deps.json +C:\KIT\Kit.Core\LibExternal\Npgsql\bin\Debug\net8.0\Npgsql.dll +C:\KIT\Kit.Core\LibExternal\Npgsql\bin\Debug\net8.0\Npgsql.pdb +C:\KIT\Kit.Core\LibExternal\Npgsql\obj\Debug\net8.0\Npgsql.GeneratedMSBuildEditorConfig.editorconfig +C:\KIT\Kit.Core\LibExternal\Npgsql\obj\Debug\net8.0\Npgsql.AssemblyInfoInputs.cache +C:\KIT\Kit.Core\LibExternal\Npgsql\obj\Debug\net8.0\Npgsql.AssemblyInfo.cs +C:\KIT\Kit.Core\LibExternal\Npgsql\obj\Debug\net8.0\Npgsql.csproj.CoreCompileInputs.cache +C:\KIT\Kit.Core\LibExternal\Npgsql\obj\Debug\net8.0\Npgsql.dll +C:\KIT\Kit.Core\LibExternal\Npgsql\obj\Debug\net8.0\refint\Npgsql.dll +C:\KIT\Kit.Core\LibExternal\Npgsql\obj\Debug\net8.0\Npgsql.pdb +C:\KIT\Kit.Core\LibExternal\Npgsql\obj\Debug\net8.0\ref\Npgsql.dll diff --git a/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.dll b/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.dll new file mode 100644 index 0000000..dab0131 Binary files /dev/null and b/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.dll differ diff --git a/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.pdb b/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.pdb new file mode 100644 index 0000000..4ab92eb Binary files /dev/null and b/LibExternal/Npgsql/obj/Debug/net8.0/Npgsql.pdb differ diff --git a/LibExternal/Npgsql/obj/Debug/net8.0/ref/Npgsql.dll b/LibExternal/Npgsql/obj/Debug/net8.0/ref/Npgsql.dll new file mode 100644 index 0000000..9829332 Binary files /dev/null and b/LibExternal/Npgsql/obj/Debug/net8.0/ref/Npgsql.dll differ diff --git a/LibExternal/Npgsql/obj/Debug/net8.0/refint/Npgsql.dll b/LibExternal/Npgsql/obj/Debug/net8.0/refint/Npgsql.dll new file mode 100644 index 0000000..9829332 Binary files /dev/null and b/LibExternal/Npgsql/obj/Debug/net8.0/refint/Npgsql.dll differ diff --git a/Kit.Core.Helpers/obj/Kit.Core.Helpers.csproj.nuget.dgspec.json b/LibExternal/Npgsql/obj/Npgsql.csproj.nuget.dgspec.json similarity index 80% rename from Kit.Core.Helpers/obj/Kit.Core.Helpers.csproj.nuget.dgspec.json rename to LibExternal/Npgsql/obj/Npgsql.csproj.nuget.dgspec.json index c23e5d4..8ddfa89 100644 --- a/Kit.Core.Helpers/obj/Kit.Core.Helpers.csproj.nuget.dgspec.json +++ b/LibExternal/Npgsql/obj/Npgsql.csproj.nuget.dgspec.json @@ -1,17 +1,17 @@ { "format": 1, "restore": { - "C:\\KIT\\Kit.Core\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj": {} + "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\Npgsql.csproj": {} }, "projects": { - "C:\\KIT\\Kit.Core\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj": { + "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\Npgsql.csproj": { "version": "1.0.0", "restore": { - "projectUniqueName": "C:\\KIT\\Kit.Core\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj", - "projectName": "Kit.Core.Helpers", - "projectPath": "C:\\KIT\\Kit.Core\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj", + "projectUniqueName": "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\Npgsql.csproj", + "projectName": "Npgsql", + "projectPath": "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\Npgsql.csproj", "packagesPath": "C:\\Users\\user\\.nuget\\packages\\", - "outputPath": "C:\\KIT\\Kit.Core\\Kit.Core.Helpers\\obj\\", + "outputPath": "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\obj\\", "projectStyle": "PackageReference", "fallbackFolders": [ "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" @@ -26,7 +26,8 @@ ], "sources": { "C:\\GIT\\RiskProf.Modules.Core\\RiskProf.LK.Back\\RiskProf.LK.Back\\packages": {}, - "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {} + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} }, "frameworks": { "net8.0": { diff --git a/LibExternal/Npgsql/obj/Npgsql.csproj.nuget.g.props b/LibExternal/Npgsql/obj/Npgsql.csproj.nuget.g.props new file mode 100644 index 0000000..020441f --- /dev/null +++ b/LibExternal/Npgsql/obj/Npgsql.csproj.nuget.g.props @@ -0,0 +1,16 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\user\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages + PackageReference + 6.14.0 + + + + + + \ No newline at end of file diff --git a/LibExternal/Npgsql/obj/Npgsql.csproj.nuget.g.targets b/LibExternal/Npgsql/obj/Npgsql.csproj.nuget.g.targets new file mode 100644 index 0000000..3dc06ef --- /dev/null +++ b/LibExternal/Npgsql/obj/Npgsql.csproj.nuget.g.targets @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/Kit.Core.Helpers/obj/project.assets.json b/LibExternal/Npgsql/obj/project.assets.json similarity index 86% rename from Kit.Core.Helpers/obj/project.assets.json rename to LibExternal/Npgsql/obj/project.assets.json index 6af60d6..38096ca 100644 --- a/Kit.Core.Helpers/obj/project.assets.json +++ b/LibExternal/Npgsql/obj/project.assets.json @@ -14,11 +14,11 @@ "project": { "version": "1.0.0", "restore": { - "projectUniqueName": "C:\\KIT\\Kit.Core\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj", - "projectName": "Kit.Core.Helpers", - "projectPath": "C:\\KIT\\Kit.Core\\Kit.Core.Helpers\\Kit.Core.Helpers.csproj", + "projectUniqueName": "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\Npgsql.csproj", + "projectName": "Npgsql", + "projectPath": "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\Npgsql.csproj", "packagesPath": "C:\\Users\\user\\.nuget\\packages\\", - "outputPath": "C:\\KIT\\Kit.Core\\Kit.Core.Helpers\\obj\\", + "outputPath": "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\obj\\", "projectStyle": "PackageReference", "fallbackFolders": [ "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" @@ -33,7 +33,8 @@ ], "sources": { "C:\\GIT\\RiskProf.Modules.Core\\RiskProf.LK.Back\\RiskProf.LK.Back\\packages": {}, - "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {} + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} }, "frameworks": { "net8.0": { diff --git a/LibExternal/Npgsql/obj/project.nuget.cache b/LibExternal/Npgsql/obj/project.nuget.cache new file mode 100644 index 0000000..7193f5b --- /dev/null +++ b/LibExternal/Npgsql/obj/project.nuget.cache @@ -0,0 +1,8 @@ +{ + "version": 2, + "dgSpecHash": "HV8HvfHmHFw=", + "success": true, + "projectFilePath": "C:\\KIT\\Kit.Core\\LibExternal\\Npgsql\\Npgsql.csproj", + "expectedPackageFiles": [], + "logs": [] +} \ No newline at end of file diff --git a/LibExternal/System.IO.Hashing/BinaryPrimitives.cs b/LibExternal/System.IO.Hashing/BinaryPrimitives.cs new file mode 100644 index 0000000..9254c2c --- /dev/null +++ b/LibExternal/System.IO.Hashing/BinaryPrimitives.cs @@ -0,0 +1,85 @@ +using System; + +namespace System.IO.Hashing +{ + public static class BinaryPrimitives + { + public static void WriteUInt32LittleEndian(Span destination, uint value) + { + if (destination.Length < 4) + { + throw new ArgumentOutOfRangeException(); + } + + destination[3] = (byte)(value >> 24); + destination[2] = (byte)(value >> 16); + destination[1] = (byte)(value >> 8); + destination[0] = (byte)value; + } + } + + public sealed partial class Crc32 + { + private const uint InitialState = 0xFFFF_FFFFu; + private const int Size = sizeof(uint); + private static readonly uint[] s_crcLookup = GenerateReflectedTable(0xEDB88320u); + + private static uint[] GenerateReflectedTable(uint reflectedPolynomial) + { + uint[] table = new uint[256]; + + for (int i = 0; i < 256; i++) + { + uint val = (uint)i; + + for (int j = 0; j < 8; j++) + { + if ((val & 0b0000_0001) == 0) + { + val >>= 1; + } + else + { + val = (val >> 1) ^ reflectedPolynomial; + } + } + + table[i] = val; + } + + return table; + } + + public static bool TryHash(ReadOnlySpan source, Span destination, out int bytesWritten) + { + if (destination.Length < Size) + { + bytesWritten = 0; + return false; + } + + bytesWritten = StaticHash(source, destination); + return true; + } + + private static int StaticHash(ReadOnlySpan source, Span destination) + { + uint crc = InitialState; + crc = Update(crc, source); + BinaryPrimitives.WriteUInt32LittleEndian(destination, ~crc); + return Size; + } + + private static uint Update(uint crc, ReadOnlySpan source) + { + for (int i = 0; i < source.Length; i++) + { + byte idx = (byte)crc; + idx ^= source[i]; + crc = s_crcLookup[idx] ^ (crc >> 8); + } + + return crc; + } + } +} diff --git a/LibExternal/System.IO.Hashing/System.IO.Hashing.csproj b/LibExternal/System.IO.Hashing/System.IO.Hashing.csproj new file mode 100644 index 0000000..ae2821e --- /dev/null +++ b/LibExternal/System.IO.Hashing/System.IO.Hashing.csproj @@ -0,0 +1,7 @@ + + + + net8.0 + + + diff --git a/LibExternal/System.IO.Hashing/bin/Debug/net8.0/System.IO.Hashing.deps.json b/LibExternal/System.IO.Hashing/bin/Debug/net8.0/System.IO.Hashing.deps.json new file mode 100644 index 0000000..0f919e0 --- /dev/null +++ b/LibExternal/System.IO.Hashing/bin/Debug/net8.0/System.IO.Hashing.deps.json @@ -0,0 +1,23 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v8.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v8.0": { + "System.IO.Hashing/1.0.0": { + "runtime": { + "System.IO.Hashing.dll": {} + } + } + } + }, + "libraries": { + "System.IO.Hashing/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/LibExternal/System.IO.Hashing/bin/Debug/net8.0/System.IO.Hashing.dll b/LibExternal/System.IO.Hashing/bin/Debug/net8.0/System.IO.Hashing.dll new file mode 100644 index 0000000..2888d97 Binary files /dev/null and b/LibExternal/System.IO.Hashing/bin/Debug/net8.0/System.IO.Hashing.dll differ diff --git a/LibExternal/System.IO.Hashing/bin/Debug/net8.0/System.IO.Hashing.pdb b/LibExternal/System.IO.Hashing/bin/Debug/net8.0/System.IO.Hashing.pdb new file mode 100644 index 0000000..1342663 Binary files /dev/null and b/LibExternal/System.IO.Hashing/bin/Debug/net8.0/System.IO.Hashing.pdb differ diff --git a/LibExternal/System.IO.Hashing/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs b/LibExternal/System.IO.Hashing/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs new file mode 100644 index 0000000..2217181 --- /dev/null +++ b/LibExternal/System.IO.Hashing/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")] diff --git a/LibExternal/System.IO.Hashing/obj/Debug/net8.0/System.IO.Hashing.AssemblyInfo.cs b/LibExternal/System.IO.Hashing/obj/Debug/net8.0/System.IO.Hashing.AssemblyInfo.cs new file mode 100644 index 0000000..b3a6721 --- /dev/null +++ b/LibExternal/System.IO.Hashing/obj/Debug/net8.0/System.IO.Hashing.AssemblyInfo.cs @@ -0,0 +1,23 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("System.IO.Hashing")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+37e709e30d2205dd559831f65211b8dd5d40a303")] +[assembly: System.Reflection.AssemblyProductAttribute("System.IO.Hashing")] +[assembly: System.Reflection.AssemblyTitleAttribute("System.IO.Hashing")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/LibExternal/System.IO.Hashing/obj/Debug/net8.0/System.IO.Hashing.AssemblyInfoInputs.cache b/LibExternal/System.IO.Hashing/obj/Debug/net8.0/System.IO.Hashing.AssemblyInfoInputs.cache new file mode 100644 index 0000000..d72065c --- /dev/null +++ b/LibExternal/System.IO.Hashing/obj/Debug/net8.0/System.IO.Hashing.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +3dd1152bb395231cfe14de746bbcf12892fe77d3b8d1b88cda617dee5af784fe diff --git a/LibExternal/System.IO.Hashing/obj/Debug/net8.0/System.IO.Hashing.GeneratedMSBuildEditorConfig.editorconfig b/LibExternal/System.IO.Hashing/obj/Debug/net8.0/System.IO.Hashing.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..5bea263 --- /dev/null +++ b/LibExternal/System.IO.Hashing/obj/Debug/net8.0/System.IO.Hashing.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,15 @@ +is_global = true +build_property.TargetFramework = net8.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = System.IO.Hashing +build_property.ProjectDir = C:\KIT\Kit.Core\LibExternal\System.IO.Hashing\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 8.0 +build_property.EnableCodeStyleSeverity = diff --git a/LibExternal/System.IO.Hashing/obj/Debug/net8.0/System.IO.Hashing.assets.cache b/LibExternal/System.IO.Hashing/obj/Debug/net8.0/System.IO.Hashing.assets.cache new file mode 100644 index 0000000..5972b00 Binary files /dev/null and b/LibExternal/System.IO.Hashing/obj/Debug/net8.0/System.IO.Hashing.assets.cache differ diff --git a/LibExternal/System.IO.Hashing/obj/Debug/net8.0/System.IO.Hashing.csproj.CoreCompileInputs.cache b/LibExternal/System.IO.Hashing/obj/Debug/net8.0/System.IO.Hashing.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..c800de2 --- /dev/null +++ b/LibExternal/System.IO.Hashing/obj/Debug/net8.0/System.IO.Hashing.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +a85bad0c56e4630d0ed489bc7ba87a54eee8fbfe2ddbe817a36ab5acfcbecb32 diff --git a/LibExternal/System.IO.Hashing/obj/Debug/net8.0/System.IO.Hashing.csproj.FileListAbsolute.txt b/LibExternal/System.IO.Hashing/obj/Debug/net8.0/System.IO.Hashing.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..aeeada4 --- /dev/null +++ b/LibExternal/System.IO.Hashing/obj/Debug/net8.0/System.IO.Hashing.csproj.FileListAbsolute.txt @@ -0,0 +1,11 @@ +C:\KIT\Kit.Core\LibExternal\System.IO.Hashing\bin\Debug\net8.0\System.IO.Hashing.deps.json +C:\KIT\Kit.Core\LibExternal\System.IO.Hashing\bin\Debug\net8.0\System.IO.Hashing.dll +C:\KIT\Kit.Core\LibExternal\System.IO.Hashing\bin\Debug\net8.0\System.IO.Hashing.pdb +C:\KIT\Kit.Core\LibExternal\System.IO.Hashing\obj\Debug\net8.0\System.IO.Hashing.GeneratedMSBuildEditorConfig.editorconfig +C:\KIT\Kit.Core\LibExternal\System.IO.Hashing\obj\Debug\net8.0\System.IO.Hashing.AssemblyInfoInputs.cache +C:\KIT\Kit.Core\LibExternal\System.IO.Hashing\obj\Debug\net8.0\System.IO.Hashing.AssemblyInfo.cs +C:\KIT\Kit.Core\LibExternal\System.IO.Hashing\obj\Debug\net8.0\System.IO.Hashing.csproj.CoreCompileInputs.cache +C:\KIT\Kit.Core\LibExternal\System.IO.Hashing\obj\Debug\net8.0\System.IO.Hashing.dll +C:\KIT\Kit.Core\LibExternal\System.IO.Hashing\obj\Debug\net8.0\refint\System.IO.Hashing.dll +C:\KIT\Kit.Core\LibExternal\System.IO.Hashing\obj\Debug\net8.0\System.IO.Hashing.pdb +C:\KIT\Kit.Core\LibExternal\System.IO.Hashing\obj\Debug\net8.0\ref\System.IO.Hashing.dll diff --git a/LibExternal/System.IO.Hashing/obj/Debug/net8.0/System.IO.Hashing.dll b/LibExternal/System.IO.Hashing/obj/Debug/net8.0/System.IO.Hashing.dll new file mode 100644 index 0000000..2888d97 Binary files /dev/null and b/LibExternal/System.IO.Hashing/obj/Debug/net8.0/System.IO.Hashing.dll differ diff --git a/LibExternal/System.IO.Hashing/obj/Debug/net8.0/System.IO.Hashing.pdb b/LibExternal/System.IO.Hashing/obj/Debug/net8.0/System.IO.Hashing.pdb new file mode 100644 index 0000000..1342663 Binary files /dev/null and b/LibExternal/System.IO.Hashing/obj/Debug/net8.0/System.IO.Hashing.pdb differ diff --git a/LibExternal/System.IO.Hashing/obj/Debug/net8.0/ref/System.IO.Hashing.dll b/LibExternal/System.IO.Hashing/obj/Debug/net8.0/ref/System.IO.Hashing.dll new file mode 100644 index 0000000..37ba4ae Binary files /dev/null and b/LibExternal/System.IO.Hashing/obj/Debug/net8.0/ref/System.IO.Hashing.dll differ diff --git a/LibExternal/System.IO.Hashing/obj/Debug/net8.0/refint/System.IO.Hashing.dll b/LibExternal/System.IO.Hashing/obj/Debug/net8.0/refint/System.IO.Hashing.dll new file mode 100644 index 0000000..37ba4ae Binary files /dev/null and b/LibExternal/System.IO.Hashing/obj/Debug/net8.0/refint/System.IO.Hashing.dll differ diff --git a/LibExternal/System.IO.Hashing/obj/System.IO.Hashing.csproj.nuget.dgspec.json b/LibExternal/System.IO.Hashing/obj/System.IO.Hashing.csproj.nuget.dgspec.json new file mode 100644 index 0000000..9946d1d --- /dev/null +++ b/LibExternal/System.IO.Hashing/obj/System.IO.Hashing.csproj.nuget.dgspec.json @@ -0,0 +1,74 @@ +{ + "format": 1, + "restore": { + "C:\\KIT\\Kit.Core\\LibExternal\\System.IO.Hashing\\System.IO.Hashing.csproj": {} + }, + "projects": { + "C:\\KIT\\Kit.Core\\LibExternal\\System.IO.Hashing\\System.IO.Hashing.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\KIT\\Kit.Core\\LibExternal\\System.IO.Hashing\\System.IO.Hashing.csproj", + "projectName": "System.IO.Hashing", + "projectPath": "C:\\KIT\\Kit.Core\\LibExternal\\System.IO.Hashing\\System.IO.Hashing.csproj", + "packagesPath": "C:\\Users\\user\\.nuget\\packages\\", + "outputPath": "C:\\KIT\\Kit.Core\\LibExternal\\System.IO.Hashing\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\user\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "C:\\GIT\\RiskProf.Modules.Core\\RiskProf.LK.Back\\RiskProf.LK.Back\\packages": {}, + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.302/PortableRuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/LibExternal/System.IO.Hashing/obj/System.IO.Hashing.csproj.nuget.g.props b/LibExternal/System.IO.Hashing/obj/System.IO.Hashing.csproj.nuget.g.props new file mode 100644 index 0000000..020441f --- /dev/null +++ b/LibExternal/System.IO.Hashing/obj/System.IO.Hashing.csproj.nuget.g.props @@ -0,0 +1,16 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\user\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages + PackageReference + 6.14.0 + + + + + + \ No newline at end of file diff --git a/LibExternal/System.IO.Hashing/obj/System.IO.Hashing.csproj.nuget.g.targets b/LibExternal/System.IO.Hashing/obj/System.IO.Hashing.csproj.nuget.g.targets new file mode 100644 index 0000000..3dc06ef --- /dev/null +++ b/LibExternal/System.IO.Hashing/obj/System.IO.Hashing.csproj.nuget.g.targets @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/LibExternal/System.IO.Hashing/obj/project.assets.json b/LibExternal/System.IO.Hashing/obj/project.assets.json new file mode 100644 index 0000000..7f58579 --- /dev/null +++ b/LibExternal/System.IO.Hashing/obj/project.assets.json @@ -0,0 +1,80 @@ +{ + "version": 3, + "targets": { + "net8.0": {} + }, + "libraries": {}, + "projectFileDependencyGroups": { + "net8.0": [] + }, + "packageFolders": { + "C:\\Users\\user\\.nuget\\packages\\": {}, + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\KIT\\Kit.Core\\LibExternal\\System.IO.Hashing\\System.IO.Hashing.csproj", + "projectName": "System.IO.Hashing", + "projectPath": "C:\\KIT\\Kit.Core\\LibExternal\\System.IO.Hashing\\System.IO.Hashing.csproj", + "packagesPath": "C:\\Users\\user\\.nuget\\packages\\", + "outputPath": "C:\\KIT\\Kit.Core\\LibExternal\\System.IO.Hashing\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\user\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "C:\\GIT\\RiskProf.Modules.Core\\RiskProf.LK.Back\\RiskProf.LK.Back\\packages": {}, + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.302/PortableRuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/LibExternal/System.IO.Hashing/obj/project.nuget.cache b/LibExternal/System.IO.Hashing/obj/project.nuget.cache new file mode 100644 index 0000000..511e450 --- /dev/null +++ b/LibExternal/System.IO.Hashing/obj/project.nuget.cache @@ -0,0 +1,8 @@ +{ + "version": 2, + "dgSpecHash": "FSBjgLHMDG0=", + "success": true, + "projectFilePath": "C:\\KIT\\Kit.Core\\LibExternal\\System.IO.Hashing\\System.IO.Hashing.csproj", + "expectedPackageFiles": [], + "logs": [] +} \ No newline at end of file diff --git a/LibExternal/System.Reactive/AnonymousObservable.cs b/LibExternal/System.Reactive/AnonymousObservable.cs new file mode 100644 index 0000000..ada2980 --- /dev/null +++ b/LibExternal/System.Reactive/AnonymousObservable.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; + +namespace System.Reactive +{ + /// + /// Class to create an instance from a delegate-based implementation of the method. + /// + /// The type of the elements in the sequence. + public sealed class AnonymousObservable : ObservableBase + { + private readonly Func, IDisposable> _subscribe; + + /// + /// Creates an observable sequence object from the specified subscription function. + /// + /// method implementation. + /// is null. + public AnonymousObservable(Func, IDisposable> subscribe) + { + _subscribe = subscribe ?? throw new ArgumentNullException(nameof(subscribe)); + } + + /// + /// Calls the subscription function that was supplied to the constructor. + /// + /// Observer to send notifications to. + /// Disposable object representing an observer's subscription to the observable sequence. + protected override IDisposable SubscribeCore(IObserver observer) + { + return _subscribe(observer) ?? Disposable.Empty; + } + } +} diff --git a/LibExternal/System.Reactive/AnonymousObserver.cs b/LibExternal/System.Reactive/AnonymousObserver.cs new file mode 100644 index 0000000..ddf82eb --- /dev/null +++ b/LibExternal/System.Reactive/AnonymousObserver.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive +{ + /// + /// Class to create an instance from delegate-based implementations of the On* methods. + /// + /// The type of the elements in the sequence. + public sealed class AnonymousObserver : ObserverBase + { + private readonly Action _onNext; + private readonly Action _onError; + private readonly Action _onCompleted; + + /// + /// Creates an observer from the specified , , and actions. + /// + /// Observer's action implementation. + /// Observer's action implementation. + /// Observer's action implementation. + /// or or is null. + public AnonymousObserver(Action onNext, Action onError, Action onCompleted) + { + _onNext = onNext ?? throw new ArgumentNullException(nameof(onNext)); + _onError = onError ?? throw new ArgumentNullException(nameof(onError)); + _onCompleted = onCompleted ?? throw new ArgumentNullException(nameof(onCompleted)); + } + + /// + /// Creates an observer from the specified action. + /// + /// Observer's action implementation. + /// is null. + public AnonymousObserver(Action onNext) + : this(onNext, Stubs.Throw, Stubs.Nop) + { + } + + /// + /// Creates an observer from the specified and actions. + /// + /// Observer's action implementation. + /// Observer's action implementation. + /// or is null. + public AnonymousObserver(Action onNext, Action onError) + : this(onNext, onError, Stubs.Nop) + { + } + + /// + /// Creates an observer from the specified and actions. + /// + /// Observer's action implementation. + /// Observer's action implementation. + /// or is null. + public AnonymousObserver(Action onNext, Action onCompleted) + : this(onNext, Stubs.Throw, onCompleted) + { + } + + /// + /// Calls the action implementing . + /// + /// Next element in the sequence. + protected override void OnNextCore(T value) => _onNext(value); + + /// + /// Calls the action implementing . + /// + /// The error that has occurred. + protected override void OnErrorCore(Exception error) => _onError(error); + + /// + /// Calls the action implementing . + /// + protected override void OnCompletedCore() => _onCompleted(); + + internal ISafeObserver MakeSafe() => new AnonymousSafeObserver(_onNext, _onError, _onCompleted); + } +} diff --git a/LibExternal/System.Reactive/AnonymousSafeObserver.cs b/LibExternal/System.Reactive/AnonymousSafeObserver.cs new file mode 100644 index 0000000..0adda2a --- /dev/null +++ b/LibExternal/System.Reactive/AnonymousSafeObserver.cs @@ -0,0 +1,78 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Threading; + +namespace System.Reactive +{ + // + // See AutoDetachObserver.cs for more information on the safeguarding requirement and + // its implementation aspects. + // + + /// + /// This class fuses logic from ObserverBase, AnonymousObserver, and SafeObserver into one class. When an observer + /// needs to be safeguarded, an instance of this type can be created by SafeObserver.Create when it detects its + /// input is an AnonymousObserver, which is commonly used by end users when using the Subscribe extension methods + /// that accept delegates for the On* handlers. By doing the fusion, we make the call stack depth shorter which + /// helps debugging and some performance. + /// + internal sealed class AnonymousSafeObserver : SafeObserver + { + private readonly Action _onNext; + private readonly Action _onError; + private readonly Action _onCompleted; + + private int _isStopped; + + public AnonymousSafeObserver(Action onNext, Action onError, Action onCompleted) + { + _onNext = onNext; + _onError = onError; + _onCompleted = onCompleted; + } + + public override void OnNext(T value) + { + if (_isStopped == 0) + { + var noError = false; + try + { + _onNext(value); + noError = true; + } + finally + { + if (!noError) + { + Dispose(); + } + } + } + } + + public override void OnError(Exception error) + { + if (Interlocked.Exchange(ref _isStopped, 1) == 0) + { + using (this) + { + _onError(error); + } + } + } + + public override void OnCompleted() + { + if (Interlocked.Exchange(ref _isStopped, 1) == 0) + { + using (this) + { + _onCompleted(); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/AsyncLock.cs b/LibExternal/System.Reactive/Concurrency/AsyncLock.cs new file mode 100644 index 0000000..9bfce6e --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/AsyncLock.cs @@ -0,0 +1,139 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive.Concurrency +{ + /// + /// Asynchronous lock. + /// + public sealed class AsyncLock : IDisposable + { + private bool _isAcquired; + private bool _hasFaulted; + private readonly object _guard = new(); + private Queue<(Action action, Delegate @delegate, object? state)>? _queue; + + /// + /// Queues the action for execution. If the caller acquires the lock and becomes the owner, + /// the queue is processed. If the lock is already owned, the action is queued and will get + /// processed by the owner. + /// + /// Action to queue for execution. + /// is null. + public void Wait(Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + Wait(action, static closureAction => closureAction!()); + } + + /// + /// Queues the action for execution. If the caller acquires the lock and becomes the owner, + /// the queue is processed. If the lock is already owned, the action is queued and will get + /// processed by the owner. + /// + /// Action to queue for execution. + /// The state to pass to the action when it gets invoked under the lock. + /// is null. + /// In case TState is a value type, this operation will involve boxing of . + /// However, this is often an improvement over the allocation of a closure object and a delegate. + internal void Wait(TState state, Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + Wait(state, action, static (actionObject, stateObject) => ((Action)actionObject)((TState)stateObject!)); + } + + private void Wait(object? state, Delegate @delegate, Action action) + { + // allow one thread to update the state + lock (_guard) + { + // if a previous action crashed, ignore any future actions + if (_hasFaulted) + { + return; + } + + // if the "lock" is busy, queue up the extra work + // otherwise there is no need to queue up "action" + if (_isAcquired) + { + // create the queue if necessary + var q = _queue; + if (q == null) + { + q = new Queue<(Action action, Delegate @delegate, object? state)>(); + _queue = q; + } + // enqueue the work + q.Enqueue((action, @delegate, state)); + return; + } + + // indicate there is processing going on + _isAcquired = true; + } + + // if we get here, execute the "action" first + + for (; ; ) + { + try + { + action(@delegate, state); + } + catch + { + // the execution failed, terminate this AsyncLock + lock (_guard) + { + // throw away the queue + _queue = null; + // report fault + _hasFaulted = true; + } + throw; + } + + // execution succeeded, let's see if more work has to be done + lock (_guard) + { + var q = _queue; + // either there is no queue yet or we run out of work + if (q == null || q.Count == 0) + { + // release the lock + _isAcquired = false; + return; + } + + // get the next work action + (action, @delegate, state) = q.Dequeue(); + } + // loop back and execute the action + } + } + + /// + /// Clears the work items in the queue and drops further work being queued. + /// + public void Dispose() + { + lock (_guard) + { + _queue = null; + _hasFaulted = true; + } + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/CatchScheduler.cs b/LibExternal/System.Reactive/Concurrency/CatchScheduler.cs new file mode 100644 index 0000000..96c7091 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/CatchScheduler.cs @@ -0,0 +1,168 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; +using System.Runtime.CompilerServices; + +namespace System.Reactive.Concurrency +{ + internal sealed class CatchScheduler : SchedulerWrapper + where TException : Exception + { + private readonly Func _handler; + + public CatchScheduler(IScheduler scheduler, Func handler) + : base(scheduler) + { + _handler = handler; + } + + protected override Func Wrap(Func action) + { + return (self, state) => + { + try + { + return action(GetRecursiveWrapper(self), state); + } + catch (TException exception) when (_handler(exception)) + { + return Disposable.Empty; + } + }; + } + + public CatchScheduler(IScheduler scheduler, Func handler, ConditionalWeakTable cache) + : base(scheduler, cache) + { + _handler = handler; + } + + protected override SchedulerWrapper Clone(IScheduler scheduler, ConditionalWeakTable cache) + { + return new CatchScheduler(scheduler, _handler, cache); + } + + protected override bool TryGetService(IServiceProvider provider, Type serviceType, out object? service) + { + service = provider.GetService(serviceType); + + if (service != null) + { + if (serviceType == typeof(ISchedulerLongRunning)) + { + service = new CatchSchedulerLongRunning((ISchedulerLongRunning)service, _handler); + } + else if (serviceType == typeof(ISchedulerPeriodic)) + { + service = new CatchSchedulerPeriodic((ISchedulerPeriodic)service, _handler); + } + } + + return true; + } + + private class CatchSchedulerLongRunning : ISchedulerLongRunning + { + private readonly ISchedulerLongRunning _scheduler; + private readonly Func _handler; + + public CatchSchedulerLongRunning(ISchedulerLongRunning scheduler, Func handler) + { + _scheduler = scheduler; + _handler = handler; + } + + public IDisposable ScheduleLongRunning(TState state, Action action) + { + // Note that avoiding closure allocation here would introduce infinite generic recursion over the TState argument + + return _scheduler.ScheduleLongRunning( + state, + (state1, cancel) => + { + try + { + action(state1, cancel); + } + catch (TException exception) when (_handler(exception)) + { + } + }); + } + } + + private sealed class CatchSchedulerPeriodic : ISchedulerPeriodic + { + private sealed class PeriodicallyScheduledWorkItem : IDisposable + { + private SingleAssignmentDisposableValue _cancel; + private bool _failed; + + private readonly Func _action; + private readonly CatchSchedulerPeriodic _catchScheduler; + + public PeriodicallyScheduledWorkItem(CatchSchedulerPeriodic scheduler, TState state, TimeSpan period, Func action) + { + _catchScheduler = scheduler; + _action = action; + + // Note that avoiding closure allocation here would introduce infinite generic recursion over the TState argument + _cancel.Disposable = scheduler._scheduler.SchedulePeriodic(state, period, state1 => Tick(state1).state); + } + + public void Dispose() + { + _cancel.Dispose(); + } + + private (PeriodicallyScheduledWorkItem @this, TState state) Tick(TState state) + { + // + // Cancellation may not be granted immediately; prevent from running user + // code in that case. Periodic schedulers are assumed to introduce some + // degree of concurrency, so we should return from the SchedulePeriodic + // call eventually, allowing the d.Dispose() call in the catch block to + // take effect. + // + if (_failed) + { + return default; + } + + try + { + return (this, _action(state)); + } + catch (TException exception) + { + _failed = true; + + if (!_catchScheduler._handler(exception)) + { + throw; + } + + _cancel.Dispose(); + return default; + } + } + } + + private readonly ISchedulerPeriodic _scheduler; + private readonly Func _handler; + + public CatchSchedulerPeriodic(ISchedulerPeriodic scheduler, Func handler) + { + _scheduler = scheduler; + _handler = handler; + } + + public IDisposable SchedulePeriodic(TState state, TimeSpan period, Func action) + { + return new PeriodicallyScheduledWorkItem(this, state, period, action); + } + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/ConcurrencyAbstractionLayer.cs b/LibExternal/System.Reactive/Concurrency/ConcurrencyAbstractionLayer.cs new file mode 100644 index 0000000..8ddd786 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/ConcurrencyAbstractionLayer.cs @@ -0,0 +1,90 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Reactive.PlatformServices; + +namespace System.Reactive.Concurrency +{ + /// + /// (Infrastructure) Concurrency abstraction layer. + /// + internal static class ConcurrencyAbstractionLayer + { + /// + /// Gets the current CAL. If no CAL has been set yet, it will be initialized to the default. + /// + public static IConcurrencyAbstractionLayer Current { get; } = Initialize(); + + private static IConcurrencyAbstractionLayer Initialize() + { + // + // NB: For compat reasons, we allow null to leak here. Bad things will happen but we don't want + // to trigger an exception earlier than we did before. The only case where this can happen + // is when a custom PEP is installed, which is very rare (e.g. debugger, service hosting). + // + return PlatformEnlightenmentProvider.Current.GetService()!; + } + } + + /// + /// (Infrastructure) Concurrency abstraction layer interface. + /// + /// + /// This type is used by the Rx infrastructure and not meant for public consumption or implementation. + /// No guarantees are made about forward compatibility of the type's functionality and its usage. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public interface IConcurrencyAbstractionLayer + { + /// + /// Queues a method for execution at the specified relative time. + /// + /// Method to execute. + /// State to pass to the method. + /// Time to execute the method on. + /// Disposable object that can be used to stop the timer. + IDisposable StartTimer(Action action, object? state, TimeSpan dueTime); + + /// + /// Queues a method for periodic execution based on the specified period. + /// + /// Method to execute; should be safe for reentrancy. + /// Period for running the method periodically. + /// Disposable object that can be used to stop the timer. + IDisposable StartPeriodicTimer(Action action, TimeSpan period); + + /// + /// Queues a method for execution. + /// + /// Method to execute. + /// State to pass to the method. + /// Disposable object that can be used to cancel the queued method. + IDisposable QueueUserWorkItem(Action action, object? state); + + /// + /// Blocking sleep operation. + /// + /// Time to sleep. + void Sleep(TimeSpan timeout); + + /// + /// Starts a new stopwatch object. + /// + /// New stopwatch object; started at the time of the request. + IStopwatch StartStopwatch(); + + /// + /// Gets whether long-running scheduling is supported. + /// + bool SupportsLongRunning { get; } + + /// + /// Starts a new long-running thread. + /// + /// Method to execute. + /// State to pass to the method. + void StartThread(Action action, object? state); + } +} diff --git a/LibExternal/System.Reactive/Concurrency/ConcurrencyAbstractionLayerImpl.cs b/LibExternal/System.Reactive/Concurrency/ConcurrencyAbstractionLayerImpl.cs new file mode 100644 index 0000000..d260909 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/ConcurrencyAbstractionLayerImpl.cs @@ -0,0 +1,252 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; +using System.Threading; + +namespace System.Reactive.Concurrency +{ + // + // WARNING: This code is kept *identically* in two places. One copy is kept in System.Reactive.Core for non-PLIB platforms. + // Another copy is kept in System.Reactive.PlatformServices to enlighten the default lowest common denominator + // behavior of Rx for PLIB when used on a more capable platform. + // + internal sealed class /*Default*/ConcurrencyAbstractionLayerImpl : IConcurrencyAbstractionLayer + { + private sealed class WorkItem + { + public WorkItem(Action action, object? state) + { + Action = action; + State = state; + } + + public Action Action { get; } + public object? State { get; } + } + + public IDisposable StartTimer(Action action, object? state, TimeSpan dueTime) => new Timer(action, state, Normalize(dueTime)); + + public IDisposable StartPeriodicTimer(Action action, TimeSpan period) + { + if (period < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(period)); + } + + // + // The contract for periodic scheduling in Rx is that specifying TimeSpan.Zero as the period causes the scheduler to + // call back periodically as fast as possible, sequentially. + // + if (period == TimeSpan.Zero) + { + return new FastPeriodicTimer(action); + } + + return new PeriodicTimer(action, period); + } + + public IDisposable QueueUserWorkItem(Action action, object? state) + { + ThreadPool.QueueUserWorkItem(static itemObject => + { + var item = (WorkItem)itemObject!; + + item.Action(item.State); + }, new WorkItem(action, state)); + + return Disposable.Empty; + } + + public void Sleep(TimeSpan timeout) => Thread.Sleep(Normalize(timeout)); + + public IStopwatch StartStopwatch() => new StopwatchImpl(); + + public bool SupportsLongRunning => true; + + public void StartThread(Action action, object? state) + { + new Thread(static itemObject => + { + var item = (WorkItem)itemObject!; + + item.Action(item.State); + }) + { IsBackground = true }.Start(new WorkItem(action, state)); + } + + private static TimeSpan Normalize(TimeSpan dueTime) => dueTime < TimeSpan.Zero ? TimeSpan.Zero : dueTime; + + // + // Some historical context. In the early days of Rx, we discovered an issue with + // the rooting of timers, causing them to get GC'ed even when the IDisposable of + // a scheduled activity was kept alive. The original code simply created a timer + // as follows: + // + // var t = default(Timer); + // t = new Timer(_ => + // { + // t = null; + // Debug.WriteLine("Hello!"); + // }, null, 5000, Timeout.Infinite); + // + // IIRC the reference to "t" captured by the closure wasn't sufficient on .NET CF + // to keep the timer rooted, causing problems on Windows Phone 7. As a result, we + // added rooting code using a dictionary (SD 7280), which we carried forward all + // the way to Rx v2.0 RTM. + // + // However, the desktop CLR's implementation of System.Threading.Timer exhibits + // other characteristics where a timer can root itself when the timer is still + // reachable through the state or callback parameters. To illustrate this, run + // the following piece of code: + // + // static void Main() + // { + // Bar(); + // + // while (true) + // { + // GC.Collect(); + // GC.WaitForPendingFinalizers(); + // Thread.Sleep(100); + // } + // } + // + // static void Bar() + // { + // var t = default(Timer); + // t = new Timer(_ => + // { + // t = null; // Comment out this line to see the timer stop + // Console.WriteLine("Hello!"); + // }, null, 5000, Timeout.Infinite); + // } + // + // When the closure over "t" is removed, the timer will stop automatically upon + // garbage collection. However, when retaining the reference, this problem does + // not exist. The code below exploits this behavior, avoiding unnecessary costs + // to root timers in a thread-safe manner. + // + // Below is a fragment of SOS output, proving the proper rooting: + // + // !gcroot 02492440 + // HandleTable: + // 005a13fc (pinned handle) + // -> 03491010 System.Object[] + // -> 024924dc System.Threading.TimerQueue + // -> 02492450 System.Threading.TimerQueueTimer + // -> 02492420 System.Threading.TimerCallback + // -> 02492414 TimerRootingExperiment.Program+<>c__DisplayClass1 + // -> 02492440 System.Threading.Timer + // + // With the USE_TIMER_SELF_ROOT symbol, we shake off this additional rooting code + // for newer platforms where this no longer needed. We checked this on .NET Core + // as well as .NET 4.0, and only #define this symbol for those platforms. + // + // NB: 4/13/2017 - All target platforms for the 4.x release have the self-rooting + // behavior described here, so we removed the USE_TIMER_SELF_ROOT + // symbol. + // + + private sealed class Timer : IDisposable + { + private volatile object? _state; + private Action _action; + private SingleAssignmentDisposableValue _timer; + + private static readonly object DisposedState = new(); + + public Timer(Action action, object? state, TimeSpan dueTime) + { + _state = state; + _action = action; + + _timer.Disposable = new System.Threading.Timer(static @this => ((Timer)@this!).Tick(), this, dueTime, TimeSpan.FromMilliseconds(Timeout.Infinite)); + } + + private void Tick() + { + try + { + var timerState = _state; + if (timerState != DisposedState) + { + _action(timerState); + } + } + finally + { + _timer.Dispose(); + } + } + + public void Dispose() + { + _timer.Dispose(); + + _action = Stubs.Ignore; + _state = DisposedState; + } + } + + private sealed class PeriodicTimer : IDisposable + { + private Action _action; + private volatile System.Threading.Timer? _timer; + + public PeriodicTimer(Action action, TimeSpan period) + { + _action = action; + + // + // Rooting of the timer happens through the timer's state + // which is the current instance and has a field to store the Timer instance. + // + _timer = new System.Threading.Timer(static @this => ((PeriodicTimer)@this!).Tick(), this, period, period); + } + + private void Tick() => _action(); + + public void Dispose() + { + var timer = _timer; + if (timer != null) + { + _action = Stubs.Nop; + _timer = null; + + timer.Dispose(); + } + } + } + + private sealed class FastPeriodicTimer : IDisposable + { + private readonly Action _action; + private volatile bool _disposed; + + public FastPeriodicTimer(Action action) + { + _action = action; + + new Thread(static @this => ((FastPeriodicTimer)@this!).Loop()) + { + Name = "Rx-FastPeriodicTimer", + IsBackground = true + } + .Start(this); + } + + private void Loop() + { + while (!_disposed) + { + _action(); + } + } + + public void Dispose() => _disposed = true; + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/CurrentThreadScheduler.cs b/LibExternal/System.Reactive/Concurrency/CurrentThreadScheduler.cs new file mode 100644 index 0000000..aa3bb7e --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/CurrentThreadScheduler.cs @@ -0,0 +1,171 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; + +namespace System.Reactive.Concurrency +{ + /// + /// Represents an object that schedules units of work on the current thread. + /// + /// Singleton instance of this type exposed through this static property. + public sealed class CurrentThreadScheduler : LocalScheduler + { + private static readonly Lazy StaticInstance = new(() => new CurrentThreadScheduler()); + + private CurrentThreadScheduler() + { + } + + /// + /// Gets the singleton instance of the current thread scheduler. + /// + public static CurrentThreadScheduler Instance => StaticInstance.Value; + + [ThreadStatic] + private static SchedulerQueue? _threadLocalQueue; + + [ThreadStatic] + private static IStopwatch? _clock; + + [ThreadStatic] + private static bool _running; + + private static SchedulerQueue? GetQueue() => _threadLocalQueue; + + private static void SetQueue(SchedulerQueue? newQueue) + { + _threadLocalQueue = newQueue; + } + + private static TimeSpan Time + { + get + { + _clock ??= ConcurrencyAbstractionLayer.Current.StartStopwatch(); + + return _clock.Elapsed; + } + } + + /// + /// Gets a value that indicates whether the caller must call a Schedule method. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete(Constants_Core.ObsoleteSchedulerequired)] // Preferring static method call over instance method call. + public bool ScheduleRequired => IsScheduleRequired; + + /// + /// Gets a value that indicates whether the caller must call a Schedule method. + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static bool IsScheduleRequired => !_running; + + /// + /// Schedules an action to be executed after dueTime. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// Relative time after which to execute the action. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + SchedulerQueue? queue; + + // There is no timed task and no task is currently running + if (!_running) + { + _running = true; + + if (dueTime > TimeSpan.Zero) + { + ConcurrencyAbstractionLayer.Current.Sleep(dueTime); + } + + // execute directly without queueing + IDisposable d; + try + { + d = action(this, state); + } + catch + { + SetQueue(null); + _running = false; + throw; + } + + // did recursive tasks arrive? + queue = GetQueue(); + + // yes, run those in the queue as well + if (queue != null) + { + try + { + Trampoline.Run(queue); + } + finally + { + SetQueue(null); + _running = false; + } + } + else + { + _running = false; + } + + return d; + } + + queue = GetQueue(); + + // if there is a task running or there is a queue + if (queue == null) + { + queue = new SchedulerQueue(4); + SetQueue(queue); + } + + var dt = Time + Scheduler.Normalize(dueTime); + + // queue up more work + var si = new ScheduledItem(this, state, action, dt); + queue.Enqueue(si); + return si; + } + + private static class Trampoline + { + public static void Run(SchedulerQueue queue) + { + while (queue.Count > 0) + { + var item = queue.Dequeue(); + if (!item.IsCanceled) + { + var wait = item.DueTime - Time; + if (wait.Ticks > 0) + { + ConcurrencyAbstractionLayer.Current.Sleep(wait); + } + + if (!item.IsCanceled) + { + item.Invoke(); + } + } + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/DefaultScheduler.cs b/LibExternal/System.Reactive/Concurrency/DefaultScheduler.cs new file mode 100644 index 0000000..a3220ec --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/DefaultScheduler.cs @@ -0,0 +1,208 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; + +namespace System.Reactive.Concurrency +{ + /// + /// Represents an object that schedules units of work on the platform's default scheduler. + /// + /// Singleton instance of this type exposed through this static property. + public sealed class DefaultScheduler : LocalScheduler, ISchedulerPeriodic + { + private static readonly Lazy DefaultInstance = new(() => new DefaultScheduler()); + private static readonly IConcurrencyAbstractionLayer Cal = ConcurrencyAbstractionLayer.Current; + + /// + /// Gets the singleton instance of the default scheduler. + /// + public static DefaultScheduler Instance => DefaultInstance.Value; + + private DefaultScheduler() + { + } + + /// + /// Schedules an action to be executed. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public override IDisposable Schedule(TState state, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var workItem = new UserWorkItem(this, state, action); + + workItem.CancelQueueDisposable = Cal.QueueUserWorkItem( + static closureWorkItem => ((UserWorkItem)closureWorkItem!).Run(), + workItem); + + return workItem; + } + + /// + /// Schedules an action to be executed after dueTime, using a System.Threading.Timer object. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// Relative time after which to execute the action. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var dt = Scheduler.Normalize(dueTime); + if (dt.Ticks == 0) + { + return Schedule(state, action); + } + + var workItem = new UserWorkItem(this, state, action); + + workItem.CancelQueueDisposable = Cal.StartTimer( + static closureWorkItem => ((UserWorkItem)closureWorkItem!).Run(), + workItem, + dt); + + return workItem; + } + + /// + /// Schedules a periodic piece of work, using a System.Threading.Timer object. + /// + /// The type of the state passed to the scheduled action. + /// Initial state passed to the action upon the first iteration. + /// Period for running the work periodically. + /// Action to be executed, potentially updating the state. + /// The disposable object used to cancel the scheduled recurring action (best effort). + /// is less than . + /// is null. + public IDisposable SchedulePeriodic(TState state, TimeSpan period, Func action) + { + if (period < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(period)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return new PeriodicallyScheduledWorkItem(state, period, action); + } + + private sealed class PeriodicallyScheduledWorkItem : IDisposable + { + private TState _state; + private Func _action; + private readonly IDisposable _cancel; + private readonly AsyncLock _gate = new(); + + public PeriodicallyScheduledWorkItem(TState state, TimeSpan period, Func action) + { + _state = state; + _action = action; + + _cancel = Cal.StartPeriodicTimer(Tick, period); + } + + private void Tick() + { + _gate.Wait( + this, + static closureWorkItem => closureWorkItem._state = closureWorkItem._action(closureWorkItem._state)); + } + + public void Dispose() + { + _cancel.Dispose(); + _gate.Dispose(); + _action = Stubs.I; + } + } + + + /// + /// Discovers scheduler services by interface type. + /// + /// Scheduler service interface type to discover. + /// Object implementing the requested service, if available; null otherwise. + protected override object? GetService(Type serviceType) + { + if (serviceType == typeof(ISchedulerLongRunning)) + { + if (Cal.SupportsLongRunning) + { + return LongRunning.Instance; + } + } + + return base.GetService(serviceType); + } + + private sealed class LongRunning : ISchedulerLongRunning + { + private sealed class LongScheduledWorkItem : ICancelable + { + private readonly TState _state; + private readonly Action _action; + + private SingleAssignmentDisposableValue _cancel; + + public LongScheduledWorkItem(TState state, Action action) + { + _state = state; + _action = action; + + Cal.StartThread( + thisObject => + { + var @this = (LongScheduledWorkItem)thisObject!; + + // + // Notice we don't check d.IsDisposed. The contract for ISchedulerLongRunning + // requires us to ensure the scheduled work gets an opportunity to observe + // the cancellation request. + // + @this._action(@this._state, @this); + }, + this + ); + } + + public void Dispose() + { + _cancel.Dispose(); + } + + public bool IsDisposed => _cancel.IsDisposed; + } + + public static readonly ISchedulerLongRunning Instance = new LongRunning(); + + public IDisposable ScheduleLongRunning(TState state, Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return new LongScheduledWorkItem(state, action); + } + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/DisableOptimizationsScheduler.cs b/LibExternal/System.Reactive/Concurrency/DisableOptimizationsScheduler.cs new file mode 100644 index 0000000..4ecdfc6 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/DisableOptimizationsScheduler.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using System.Runtime.CompilerServices; + +namespace System.Reactive.Concurrency +{ + internal sealed class DisableOptimizationsScheduler : SchedulerWrapper + { + private readonly Type[] _optimizationInterfaces; + + public DisableOptimizationsScheduler(IScheduler scheduler) + : base(scheduler) + { + _optimizationInterfaces = Scheduler.Optimizations; + } + + public DisableOptimizationsScheduler(IScheduler scheduler, Type[] optimizationInterfaces) + : base(scheduler) + { + _optimizationInterfaces = optimizationInterfaces; + } + + public DisableOptimizationsScheduler(IScheduler scheduler, Type[] optimizationInterfaces, ConditionalWeakTable cache) + : base(scheduler, cache) + { + _optimizationInterfaces = optimizationInterfaces; + } + + protected override SchedulerWrapper Clone(IScheduler scheduler, ConditionalWeakTable cache) + { + return new DisableOptimizationsScheduler(scheduler, _optimizationInterfaces, cache); + } + + protected override bool TryGetService(IServiceProvider provider, Type serviceType, out object? service) + { + service = null; + return _optimizationInterfaces.Contains(serviceType); + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/EventLoopScheduler.cs b/LibExternal/System.Reactive/Concurrency/EventLoopScheduler.cs new file mode 100644 index 0000000..8a13936 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/EventLoopScheduler.cs @@ -0,0 +1,399 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Disposables; +using System.Threading; + +namespace System.Reactive.Concurrency +{ + /// + /// Represents an object that schedules units of work on a designated thread. + /// + public sealed class EventLoopScheduler : LocalScheduler, ISchedulerPeriodic, IDisposable + { + #region Fields + + /// + /// Counter for diagnostic purposes, to name the threads. + /// + private static int _counter; + + /// + /// Thread factory function. + /// + private readonly Func _threadFactory; + + /// + /// Stopwatch for timing free of absolute time dependencies. + /// + private readonly IStopwatch _stopwatch; + + /// + /// Thread used by the event loop to run work items on. No work should be run on any other thread. + /// If ExitIfEmpty is set, the thread can quit and a new thread will be created when new work is scheduled. + /// + private Thread? _thread; + + /// + /// Gate to protect data structures, including the work queue and the ready list. + /// + private readonly object _gate; + + /// + /// Semaphore to count requests to re-evaluate the queue, from either Schedule requests or when a timer + /// expires and moves on to the next item in the queue. + /// + private readonly SemaphoreSlim _evt; + + /// + /// Queue holding work items. Protected by the gate. + /// + private readonly SchedulerQueue _queue; + + /// + /// Queue holding items that are ready to be run as soon as possible. Protected by the gate. + /// + private readonly Queue> _readyList; + + /// + /// Work item that will be scheduled next. Used upon reevaluation of the queue to check whether the next + /// item is still the same. If not, a new timer needs to be started (see below). + /// + private ScheduledItem? _nextItem; + + /// + /// Disposable that always holds the timer to dispatch the first element in the queue. + /// + private SerialDisposableValue _nextTimer; + + /// + /// Flag indicating whether the event loop should quit. When set, the event should be signaled as well to + /// wake up the event loop thread, which will subsequently abandon all work. + /// + private bool _disposed; + + #endregion + + #region Constructors + + /// + /// Creates an object that schedules units of work on a designated thread. + /// + public EventLoopScheduler() + : this(static a => new Thread(a) { Name = "Event Loop " + Interlocked.Increment(ref _counter), IsBackground = true }) + { + } + + /// + /// Creates an object that schedules units of work on a designated thread, using the specified factory to control thread creation options. + /// + /// Factory function for thread creation. + /// is null. + public EventLoopScheduler(Func threadFactory) + { + _threadFactory = threadFactory ?? throw new ArgumentNullException(nameof(threadFactory)); + _stopwatch = ConcurrencyAbstractionLayer.Current.StartStopwatch(); + + _gate = new object(); + + _evt = new SemaphoreSlim(0); + _queue = new SchedulerQueue(); + _readyList = new Queue>(); + + ExitIfEmpty = false; + } + + #endregion + + #region Properties + + /// + /// Indicates whether the event loop thread is allowed to quit when no work is left. If new work + /// is scheduled afterwards, a new event loop thread is created. This property is used by the + /// NewThreadScheduler which uses an event loop for its recursive invocations. + /// + internal bool ExitIfEmpty + { + get; + set; + } + + #endregion + + #region Public methods + + /// + /// Schedules an action to be executed after dueTime. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// Relative time after which to execute the action. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + /// The scheduler has been disposed and doesn't accept new work. + public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var due = _stopwatch.Elapsed + dueTime; + var si = new ScheduledItem(this, state, action, due); + + lock (_gate) + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(EventLoopScheduler)); + } + + if (dueTime <= TimeSpan.Zero) + { + _readyList.Enqueue(si); + _evt.Release(); + } + else + { + _queue.Enqueue(si); + _evt.Release(); + } + + EnsureThread(); + } + + return si; + } + + /// + /// Schedules a periodic piece of work on the designated thread. + /// + /// The type of the state passed to the scheduled action. + /// Initial state passed to the action upon the first iteration. + /// Period for running the work periodically. + /// Action to be executed, potentially updating the state. + /// The disposable object used to cancel the scheduled recurring action (best effort). + /// is null. + /// is less than . + /// The scheduler has been disposed and doesn't accept new work. + public IDisposable SchedulePeriodic(TState state, TimeSpan period, Func action) + { + if (period < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(period)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return new PeriodicallyScheduledWorkItem(this, state, period, action); + } + + private sealed class PeriodicallyScheduledWorkItem : IDisposable + { + private readonly TimeSpan _period; + private readonly Func _action; + private readonly EventLoopScheduler _scheduler; + private readonly AsyncLock _gate = new(); + + private TState _state; + private TimeSpan _next; + private MultipleAssignmentDisposableValue _task; + + public PeriodicallyScheduledWorkItem(EventLoopScheduler scheduler, TState state, TimeSpan period, Func action) + { + _state = state; + _period = period; + _action = action; + _scheduler = scheduler; + _next = scheduler._stopwatch.Elapsed + period; + + _task.TrySetFirst(scheduler.Schedule(this, _next - scheduler._stopwatch.Elapsed, static (_, s) => s.Tick(_))); + } + + private IDisposable Tick(IScheduler self) + { + _next += _period; + + _task.Disposable = self.Schedule(this, _next - _scheduler._stopwatch.Elapsed, static (_, s) => s.Tick(_)); + + _gate.Wait( + this, + static closureWorkItem => closureWorkItem._state = closureWorkItem._action(closureWorkItem._state)); + + return Disposable.Empty; + } + + public void Dispose() + { + _task.Dispose(); + _gate.Dispose(); + } + } + + /// + /// Starts a new stopwatch object. + /// + /// New stopwatch object; started at the time of the request. + public override IStopwatch StartStopwatch() + { + // + // Strictly speaking, this explicit override is not necessary because the base implementation calls into + // the enlightenment module to obtain the CAL, which would circle back to System.Reactive.PlatformServices + // where we're currently running. This is merely a short-circuit to avoid the additional roundtrip. + // + return new StopwatchImpl(); + } + + /// + /// Ends the thread associated with this scheduler. All remaining work in the scheduler queue is abandoned. + /// + public void Dispose() + { + lock (_gate) + { + if (!_disposed) + { + _disposed = true; + _nextTimer.Dispose(); + _evt.Release(); + } + } + } + + #endregion + + #region Private implementation + + /// + /// Ensures there is an event loop thread running. Should be called under the gate. + /// + private void EnsureThread() + { + if (_thread == null) + { + _thread = _threadFactory(Run); + _thread.Start(); + } + } + + /// + /// Event loop scheduled on the designated event loop thread. The loop is suspended/resumed using the event + /// which gets set by calls to Schedule, the next item timer, or calls to Dispose. + /// + private void Run() + { + while (true) + { + _evt.Wait(); + + ScheduledItem[]? ready = null; + + lock (_gate) + { + // + // Bug fix that ensures the number of calls to Release never greatly exceeds the number of calls to Wait. + // See work item #37: https://rx.codeplex.com/workitem/37 + // + while (_evt.CurrentCount > 0) + { + _evt.Wait(); + } + + // + // The event could have been set by a call to Dispose. This takes priority over anything else. We quit the + // loop immediately. Subsequent calls to Schedule won't ever create a new thread. + // + if (_disposed) + { + _evt.Dispose(); + return; + } + + while (_queue.Count > 0 && _queue.Peek().DueTime <= _stopwatch.Elapsed) + { + var item = _queue.Dequeue(); + _readyList.Enqueue(item); + } + + if (_queue.Count > 0) + { + var next = _queue.Peek(); + if (next != _nextItem) + { + _nextItem = next; + + var due = next.DueTime - _stopwatch.Elapsed; + _nextTimer.Disposable = ConcurrencyAbstractionLayer.Current.StartTimer(Tick, next, due); + } + } + + if (_readyList.Count > 0) + { + ready = _readyList.ToArray(); + _readyList.Clear(); + } + } + + if (ready != null) + { + foreach (var item in ready) + { + if (!item.IsCanceled) + { + try + { + item.Invoke(); + } + catch (ObjectDisposedException ex) when (ex.ObjectName == nameof(EventLoopScheduler)) + { + // Since we are not inside the lock at this point + // the scheduler can be disposed before the item had a chance to run + } + } + } + } + + if (ExitIfEmpty) + { + lock (_gate) + { + if (_readyList.Count == 0 && _queue.Count == 0) + { + _thread = null; + return; + } + } + } + } + } + + private void Tick(object? state) + { + lock (_gate) + { + if (!_disposed) + { + var item = (ScheduledItem)state!; + if (item == _nextItem) + { + _nextItem = null; + } + if (_queue.Remove(item)) + { + _readyList.Enqueue(item); + } + + _evt.Release(); + } + } + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Concurrency/HistoricalScheduler.cs b/LibExternal/System.Reactive/Concurrency/HistoricalScheduler.cs new file mode 100644 index 0000000..0ba493a --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/HistoricalScheduler.cs @@ -0,0 +1,159 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Diagnostics; + +namespace System.Reactive.Concurrency +{ + /// + /// Base class for historical schedulers, which are virtual time schedulers that use for absolute time and for relative time. + /// + public abstract class HistoricalSchedulerBase : VirtualTimeSchedulerBase + { + /// + /// Creates a new historical scheduler with the minimum value of as the initial clock value. + /// + protected HistoricalSchedulerBase() + : base(DateTimeOffset.MinValue, Comparer.Default) + { + } + + /// + /// Creates a new historical scheduler with the specified initial clock value. + /// + /// Initial clock value. + protected HistoricalSchedulerBase(DateTimeOffset initialClock) + : base(initialClock, Comparer.Default) + { + } + + /// + /// Creates a new historical scheduler with the specified initial clock value and absolute time comparer. + /// + /// Initial value for the clock. + /// Comparer to determine causality of events based on absolute time. + protected HistoricalSchedulerBase(DateTimeOffset initialClock, IComparer comparer) + : base(initialClock, comparer) + { + } + + /// + /// Adds a relative time value to an absolute time value. + /// + /// Absolute time value. + /// Relative time value to add. + /// The resulting absolute time sum value. + protected override DateTimeOffset Add(DateTimeOffset absolute, TimeSpan relative) + { + return absolute.Add(relative); + } + + /// + /// Converts the absolute time value to a value. + /// + /// Absolute time value to convert. + /// The corresponding value. + protected override DateTimeOffset ToDateTimeOffset(DateTimeOffset absolute) => absolute; + + /// + /// Converts the value to a relative time value. + /// + /// value to convert. + /// The corresponding relative time value. + protected override TimeSpan ToRelative(TimeSpan timeSpan) => timeSpan; + } + + /// + /// Provides a virtual time scheduler that uses for absolute time and for relative time. + /// + [DebuggerDisplay("\\{ " + + nameof(Clock) + " = {" + nameof(Clock) + "} " + + nameof(Now) + " = {" + nameof(Now) + ".ToString(\"O\")} " + + "\\}")] + public class HistoricalScheduler : HistoricalSchedulerBase + { + private readonly SchedulerQueue _queue = new(); + + /// + /// Creates a new historical scheduler with the minimum value of as the initial clock value. + /// + public HistoricalScheduler() + { + } + + /// + /// Creates a new historical scheduler with the specified initial clock value. + /// + /// Initial value for the clock. + public HistoricalScheduler(DateTimeOffset initialClock) + : base(initialClock) + { + } + + /// + /// Creates a new historical scheduler with the specified initial clock value. + /// + /// Initial value for the clock. + /// Comparer to determine causality of events based on absolute time. + /// is null. + public HistoricalScheduler(DateTimeOffset initialClock, IComparer comparer) + : base(initialClock, comparer) + { + } + + /// + /// Gets the next scheduled item to be executed. + /// + /// The next scheduled item. + protected override IScheduledItem? GetNext() + { + while (_queue.Count > 0) + { + var next = _queue.Peek(); + + if (next.IsCanceled) + { + _queue.Dequeue(); + } + else + { + return next; + } + } + + return null; + } + + /// + /// Schedules an action to be executed at . + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// Absolute time at which to execute the action. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public override IDisposable ScheduleAbsolute(TState state, DateTimeOffset dueTime, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + ScheduledItem? si = null; + + var run = new Func((scheduler, state1) => + { + _queue.Remove(si!); // NB: Assigned before function is invoked. + return action(scheduler, state1); + }); + + si = new ScheduledItem(this, state, run, dueTime, Comparer); + _queue.Enqueue(si); + + return si; + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/IScheduledItem.cs b/LibExternal/System.Reactive/Concurrency/IScheduledItem.cs new file mode 100644 index 0000000..571a735 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/IScheduledItem.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Concurrency +{ + /// + /// Represents a work item that has been scheduled. + /// + /// Absolute time representation type. + public interface IScheduledItem + { + /// + /// Gets the absolute time at which the item is due for invocation. + /// + TAbsolute DueTime { get; } + + /// + /// Invokes the work item. + /// + void Invoke(); + } +} diff --git a/LibExternal/System.Reactive/Concurrency/IScheduler.cs b/LibExternal/System.Reactive/Concurrency/IScheduler.cs new file mode 100644 index 0000000..d9b9f29 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/IScheduler.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Concurrency +{ + /// + /// Represents an object that schedules units of work. + /// + public interface IScheduler + { + /// + /// Gets the scheduler's notion of current time. + /// + DateTimeOffset Now { get; } + + /// + /// Schedules an action to be executed. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// The disposable object used to cancel the scheduled action (best effort). + IDisposable Schedule(TState state, Func action); + + /// + /// Schedules an action to be executed after dueTime. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// Relative time after which to execute the action. + /// The disposable object used to cancel the scheduled action (best effort). + IDisposable Schedule(TState state, TimeSpan dueTime, Func action); + + /// + /// Schedules an action to be executed at dueTime. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// Absolute time at which to execute the action. + /// The disposable object used to cancel the scheduled action (best effort). + IDisposable Schedule(TState state, DateTimeOffset dueTime, Func action); + } +} diff --git a/LibExternal/System.Reactive/Concurrency/ISchedulerLongRunning.cs b/LibExternal/System.Reactive/Concurrency/ISchedulerLongRunning.cs new file mode 100644 index 0000000..eebe534 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/ISchedulerLongRunning.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; + +namespace System.Reactive.Concurrency +{ + /// + /// Scheduler with support for starting long-running tasks. + /// This type of scheduler can be used to run loops more efficiently instead of using recursive scheduling. + /// + public interface ISchedulerLongRunning + { + /// + /// Schedules a long-running piece of work. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// The disposable object used to cancel the scheduled action (best effort). + /// + /// Notes to implementers + /// The returned disposable object should not prevent the work from starting, but only set the cancellation flag passed to the specified action. + /// + IDisposable ScheduleLongRunning(TState state, Action action); + } +} diff --git a/LibExternal/System.Reactive/Concurrency/ISchedulerPeriodic.cs b/LibExternal/System.Reactive/Concurrency/ISchedulerPeriodic.cs new file mode 100644 index 0000000..4a52a6f --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/ISchedulerPeriodic.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Concurrency +{ + /// + /// Scheduler with support for running periodic tasks. + /// This type of scheduler can be used to run timers more efficiently instead of using recursive scheduling. + /// + public interface ISchedulerPeriodic + { + /// + /// Schedules a periodic piece of work. + /// + /// The type of the state passed to the scheduled action. + /// Initial state passed to the action upon the first iteration. + /// Period for running the work periodically. + /// Action to be executed, potentially updating the state. + /// The disposable object used to cancel the scheduled recurring action (best effort). + IDisposable SchedulePeriodic(TState state, TimeSpan period, Func action); + } +} diff --git a/LibExternal/System.Reactive/Concurrency/IStopwatch.cs b/LibExternal/System.Reactive/Concurrency/IStopwatch.cs new file mode 100644 index 0000000..ec5961b --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/IStopwatch.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Concurrency +{ + /// + /// Abstraction for a stopwatch to compute time relative to a starting point. + /// + public interface IStopwatch + { + /// + /// Gets the time elapsed since the stopwatch object was obtained. + /// + TimeSpan Elapsed { get; } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/IStopwatchProvider.cs b/LibExternal/System.Reactive/Concurrency/IStopwatchProvider.cs new file mode 100644 index 0000000..be1f892 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/IStopwatchProvider.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Concurrency +{ + /* + * The ability to request a stopwatch object has been introduced in Rx v2.0 to reduce the + * number of allocations made by operators that use absolute time to compute relative time + * diffs, such as TimeInterval and Delay. This causes a large number of related objects to + * be allocated in the BCL, e.g. System.Globalization.DaylightTime. + */ + + /// + /// Provider for objects. + /// + public interface IStopwatchProvider + { + /// + /// Starts a new stopwatch object. + /// + /// New stopwatch object; started at the time of the request. + IStopwatch StartStopwatch(); + } +} diff --git a/LibExternal/System.Reactive/Concurrency/ImmediateScheduler.cs b/LibExternal/System.Reactive/Concurrency/ImmediateScheduler.cs new file mode 100644 index 0000000..0e792cb --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/ImmediateScheduler.cs @@ -0,0 +1,143 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; + +namespace System.Reactive.Concurrency +{ + /// + /// Represents an object that schedules units of work to run immediately on the current thread. + /// + /// Singleton instance of this type exposed through this static property. + public sealed class ImmediateScheduler : LocalScheduler + { + private static readonly Lazy StaticInstance = new(static () => new ImmediateScheduler()); + + private ImmediateScheduler() + { + } + + /// + /// Gets the singleton instance of the immediate scheduler. + /// + public static ImmediateScheduler Instance => StaticInstance.Value; + + /// + /// Schedules an action to be executed. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public override IDisposable Schedule(TState state, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return action(new AsyncLockScheduler(), state); + } + + /// + /// Schedules an action to be executed after dueTime. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// Relative time after which to execute the action. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var dt = Scheduler.Normalize(dueTime); + if (dt.Ticks > 0) + { + ConcurrencyAbstractionLayer.Current.Sleep(dt); + } + + return action(new AsyncLockScheduler(), state); + } + + private sealed class AsyncLockScheduler : LocalScheduler + { + private AsyncLock? _asyncLock; + + public override IDisposable Schedule(TState state, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var m = new SingleAssignmentDisposable(); + + _asyncLock ??= new AsyncLock(); + + _asyncLock.Wait( + (@this: this, m, action, state), + tuple => + { + if (!tuple.m.IsDisposed) + { + tuple.m.Disposable = tuple.action(tuple.@this, tuple.state); + } + }); + + return m; + } + + public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (dueTime.Ticks <= 0) + { + return Schedule(state, action); + } + + return ScheduleSlow(state, dueTime, action); + } + + private IDisposable ScheduleSlow(TState state, TimeSpan dueTime, Func action) + { + var timer = ConcurrencyAbstractionLayer.Current.StartStopwatch(); + + var m = new SingleAssignmentDisposable(); + + _asyncLock ??= new AsyncLock(); + + _asyncLock.Wait( + (@this: this, m, state, action, timer, dueTime), + tuple => + { + if (!tuple.m.IsDisposed) + { + var sleep = tuple.dueTime - tuple.timer.Elapsed; + if (sleep.Ticks > 0) + { + ConcurrencyAbstractionLayer.Current.Sleep(sleep); + } + + if (!tuple.m.IsDisposed) + { + tuple.m.Disposable = tuple.action(tuple.@this, tuple.state); + } + } + }); + + return m; + } + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/LocalScheduler.TimerQueue.cs b/LibExternal/System.Reactive/Concurrency/LocalScheduler.TimerQueue.cs new file mode 100644 index 0000000..fa004fe --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/LocalScheduler.TimerQueue.cs @@ -0,0 +1,485 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Disposables; +using System.Reactive.PlatformServices; +using System.Threading; + +namespace System.Reactive.Concurrency +{ + public partial class LocalScheduler + { + /// + /// Gate to protect local scheduler queues. + /// + private static readonly object Gate = new(); + + /// + /// Gate to protect queues and to synchronize scheduling decisions and system clock + /// change management. + /// + private static readonly object StaticGate = new(); + + /// + /// Long term work queue. Contains work that's due beyond SHORTTERM, computed at the + /// time of enqueueing. + /// + private static readonly PriorityQueue LongTerm = new(); + + /// + /// Disposable resource for the long term timer that will reevaluate and dispatch the + /// first item in the long term queue. A serial disposable is used to make "dispose + /// current and assign new" logic easier. The disposable itself is never disposed. + /// + private static readonly SerialDisposable NextLongTermTimer = new(); + + /// + /// Item at the head of the long term queue for which the current long term timer is + /// running. Used to detect changes in the queue and decide whether we should replace + /// or can continue using the current timer (because no earlier long term work was + /// added to the queue). + /// + private static WorkItem? _nextLongTermWorkItem; + + /// + /// Short term work queue. Contains work that's due soon, computed at the time of + /// enqueueing or upon reevaluation of the long term queue causing migration of work + /// items. This queue is kept in order to be able to relocate short term items back + /// to the long term queue in case a system clock change occurs. + /// + private readonly PriorityQueue _shortTerm = new(); + + /// + /// Set of disposable handles to all of the current short term work Schedule calls, + /// allowing those to be cancelled upon a system clock change. + /// + private readonly HashSet _shortTermWork = new(); + + /// + /// Threshold where an item is considered to be short term work or gets moved from + /// long term to short term. + /// + private static readonly TimeSpan ShortTerm = TimeSpan.FromSeconds(10); + + /// + /// Maximum error ratio for timer drift. We've seen machines with 10s drift on a + /// daily basis, which is in the order 10E-4, so we allow for extra margin here. + /// This value is used to calculate early arrival for the long term queue timer + /// that will reevaluate work for the short term queue. + /// + /// Example: -------------------------------...---------------------*-----$ + /// ^ ^ + /// | | + /// early due + /// 0.999 1.0 + /// + /// We also make the gap between early and due at least LONGTOSHORT so we have + /// enough time to transition work to short term and as a courtesy to the + /// destination scheduler to manage its queues etc. + /// + private const int MaxErrorRatio = 1000; + + /// + /// Minimum threshold for the long term timer to fire before the queue is reevaluated + /// for short term work. This value is chosen to be less than SHORTTERM in order to + /// ensure the timer fires and has work to transition to the short term queue. + /// + private static readonly TimeSpan LongToShort = TimeSpan.FromSeconds(5); + + /// + /// Threshold used to determine when a short term timer has fired too early compared + /// to the absolute due time. This provides a last chance protection against early + /// completion of scheduled work, which can happen in case of time adjustment in the + /// operating system (cf. GetSystemTimeAdjustment). + /// + private static readonly TimeSpan RetryShort = TimeSpan.FromMilliseconds(50); + + /// + /// Longest interval supported by timers in the BCL. + /// + private static readonly TimeSpan MaxSupportedTimer = TimeSpan.FromMilliseconds((1L << 32) - 2); + + /// + /// Creates a new local scheduler. + /// + protected LocalScheduler() + { + // + // Hook up for system clock change notifications. This doesn't do anything until the + // AddRef method is called (which can throw). + // + SystemClock.Register(this); + } + + /// + /// Enqueues absolute time scheduled work in the timer queue or the short term work list. + /// + /// State to pass to the action. + /// Absolute time to run the work on. The timer queue is responsible to execute the work close to the specified time, also accounting for system clock changes. + /// Action to run, potentially recursing into the scheduler. + /// Disposable object to prevent the work from running. + private IDisposable Enqueue(TState state, DateTimeOffset dueTime, Func action) + { + // + // Work that's due in the past is sent to the underlying scheduler through the Schedule + // overload for execution at TimeSpan.Zero. We don't go to the overload for immediate + // scheduling in order to: + // + // - Preserve the time-based nature of the call as surfaced to the underlying scheduler, + // as it may use different queuing strategies. + // + // - Optimize for the default behavior of LocalScheduler where a virtual call to Schedule + // for immediate execution calls into the abstract Schedule method with TimeSpan.Zero. + // + var due = Scheduler.Normalize(dueTime - Now); + if (due == TimeSpan.Zero) + { + return Schedule(state, TimeSpan.Zero, action); + } + + // + // We're going down the path of queueing up work or scheduling it, so we need to make + // sure we can get system clock change notifications. If not, the call below is expected + // to throw NotSupportedException. WorkItem.Invoke decreases the ref count again to allow + // the system clock monitor to stop if there's no work left. Notice work items always + // reach an execution stage since we don't dequeue items but merely mark them as cancelled + // through WorkItem.Dispose. Double execution is also prevented, so the ref count should + // correctly balance out. + // + SystemClock.AddRef(); + + var workItem = new WorkItem(this, state, dueTime, action); + + if (due <= ShortTerm) + { + ScheduleShortTermWork(workItem); + } + else + { + ScheduleLongTermWork(workItem); + } + + return workItem; + } + + /// + /// Schedule work that's due in the short term. This leads to relative scheduling calls to the + /// underlying scheduler for short TimeSpan values. If the system clock changes in the meantime, + /// the short term work is attempted to be cancelled and reevaluated. + /// + /// Work item to schedule in the short term. The caller is responsible to determine the work is indeed short term. + private void ScheduleShortTermWork(WorkItem/*!*/ item) + { + lock (Gate) + { + _shortTerm.Enqueue(item); + + // + // We don't bother trying to dequeue the item or stop the timer upon cancellation, + // but always let the timer fire to do the queue maintenance. When the item is + // cancelled, it won't run (see WorkItem.Invoke). In the event of a system clock + // change, all outstanding work in _shortTermWork is cancelled and the short + // term queue is reevaluated, potentially prompting rescheduling of short term + // work. Notice work is protected against double execution by the implementation + // of WorkItem.Invoke. + // + var d = new SingleAssignmentDisposable(); + _shortTermWork.Add(d); + + // + // We normalize the time delta again (possibly redundant), because we can't assume + // the underlying scheduler implementations is valid and deals with negative values + // (though it should). + // + var dueTime = Scheduler.Normalize(item.DueTime - item.Scheduler.Now); + d.Disposable = item.Scheduler.Schedule((@this: this, d), dueTime, static (self, tuple) => tuple.@this.ExecuteNextShortTermWorkItem(self, tuple.d)); + } + } + + /// + /// Callback to process the next short term work item. + /// + /// Recursive scheduler supplied by the underlying scheduler. + /// Disposable used to identify the work the timer was triggered for (see code for usage). + /// Empty disposable. Recursive work cancellation is wired through the original WorkItem. + private IDisposable ExecuteNextShortTermWorkItem(IScheduler scheduler, IDisposable cancel) + { + WorkItem? next = null; + + lock (Gate) + { + // + // Notice that even though we try to cancel all work in the short term queue upon a + // system clock change, cancellation may not be honored immediately and there's a + // small chance this code runs for work that has been cancelled. Because the handler + // doesn't execute the work that triggered the time-based Schedule call, but always + // runs the work from the short term queue in order, we need to make sure we're not + // stealing items in the queue. We can do so by remembering the object identity of + // the disposable and check whether it still exists in the short term work list. If + // not, a system clock change handler has gotten rid of it as part of reevaluating + // the short term queue, but we still ended up here because the inherent race in the + // call to Dispose versus the underlying timer. It's also possible the underlying + // scheduler does a bad job at cancellation, so this measure helps for that too. + // + if (_shortTermWork.Remove(cancel) && _shortTerm.Count > 0) + { + next = _shortTerm.Dequeue(); + } + } + + if (next != null) + { + // + // If things don't make sense and we're way too early to run the work, this is our + // final chance to prevent us from running before the due time. This situation can + // arise when Windows applies system clock adjustment (see SetSystemTimeAdjustment) + // and as a result the clock is ticking slower. If the clock is ticking faster due + // to such an adjustment, too bad :-). We try to minimize the window for the final + // relative time based scheduling such that 10%+ adjustments to the clock rate + // have only "little" impact (range of 100s of ms). On an absolute time scale, we + // don't provide stronger guarantees. + // + if (next.DueTime - next.Scheduler.Now >= RetryShort) + { + ScheduleShortTermWork(next); + } + else + { + // + // Invocation happens on the recursive scheduler supplied to the function. We + // are already running on the target scheduler, so we should stay on board. + // Not doing so would have unexpected behavior for e.g. NewThreadScheduler, + // causing a whole new thread to be allocated because of a top-level call to + // the Schedule method rather than a recursive one. + // + // Notice if work got cancelled, the call to Invoke will not propagate to user + // code because of the IsDisposed check inside. + // + next.Invoke(scheduler); + } + } + + // + // No need to return anything better here. We already handed out the original WorkItem + // object upon the call to Enqueue (called itself by Schedule). The disposable inside + // the work item allows a cancellation request to chase the underlying computation. + // + return Disposable.Empty; + } + + /// + /// Schedule work that's due on the long term. This leads to the work being queued up for + /// eventual transitioning to the short term work list. + /// + /// Work item to schedule on the long term. The caller is responsible to determine the work is indeed long term. + private static void ScheduleLongTermWork(WorkItem/*!*/ item) + { + lock (StaticGate) + { + LongTerm.Enqueue(item); + + // + // In case we're the first long-term item in the queue now, the timer will have + // to be updated. + // + UpdateLongTermProcessingTimer(); + } + } + + /// + /// Updates the long term timer which is responsible to transition work from the head of the + /// long term queue to the short term work list. + /// + /// Should be called under the scheduler lock. + private static void UpdateLongTermProcessingTimer() + { + /* + * CALLERS - Ensure this is called under the lock! + * + lock (s_gate) */ + { + if (LongTerm.Count == 0) + { + return; + } + + // + // To avoid setting the timer all over again for the first work item if it hasn't changed, + // we keep track of the next long term work item that will be processed by the timer. + // + var next = LongTerm.Peek(); + if (next == _nextLongTermWorkItem) + { + return; + } + + // + // We need to arrive early in order to accommodate for potential drift. The relative amount + // of drift correction is kept in MAXERRORRATIO. At the very least, we want to be LONGTOSHORT + // early to make the final jump from long term to short term, giving the target scheduler + // enough time to process the item through its queue. LONGTOSHORT is chosen such that the + // error due to drift is negligible. + // + var due = Scheduler.Normalize(next.DueTime - next.Scheduler.Now); + var remainder = TimeSpan.FromTicks(Math.Max(due.Ticks / MaxErrorRatio, LongToShort.Ticks)); + var dueEarly = due - remainder; + + // + // Limit the interval to maximum supported by underlying Timer. + // + var dueCapped = TimeSpan.FromTicks(Math.Min(dueEarly.Ticks, MaxSupportedTimer.Ticks)); + + _nextLongTermWorkItem = next; + NextLongTermTimer.Disposable = ConcurrencyAbstractionLayer.Current.StartTimer(static _ => EvaluateLongTermQueue(), null, dueCapped); + } + } + + /// + /// Evaluates the long term queue, transitioning short term work to the short term list, + /// and adjusting the new long term processing timer accordingly. + /// + private static void EvaluateLongTermQueue() + { + lock (StaticGate) + { + while (LongTerm.Count > 0) + { + var next = LongTerm.Peek(); + + var due = Scheduler.Normalize(next.DueTime - next.Scheduler.Now); + if (due >= ShortTerm) + { + break; + } + + var item = LongTerm.Dequeue(); + item.Scheduler.ScheduleShortTermWork(item); + } + + _nextLongTermWorkItem = null; + UpdateLongTermProcessingTimer(); + } + } + + /// + /// Callback invoked when a system clock change is observed in order to adjust and reevaluate + /// the internal scheduling queues. + /// + /// Currently not used. + /// Currently not used. + internal virtual void SystemClockChanged(object? sender, SystemClockChangedEventArgs args) + { + lock (StaticGate) + { + lock (Gate) + { + // + // Best-effort cancellation of short term work. A check for presence in the hash set + // is used to notice race conditions between cancellation and the timer firing (also + // guarded by the same gate object). See checks in ExecuteNextShortTermWorkItem. + // + foreach (var d in _shortTermWork) + { + d.Dispose(); + } + + _shortTermWork.Clear(); + + // + // Transition short term work to the long term queue for reevaluation by calling the + // EvaluateLongTermQueue method. We don't know which direction the clock was changed + // in, so we don't optimize for special cases, but always transition the whole queue. + // Notice the short term queue is bounded to SHORTTERM length. + // + while (_shortTerm.Count > 0) + { + var next = _shortTerm.Dequeue(); + LongTerm.Enqueue(next); + } + + // + // Reevaluate the queue and don't forget to null out the current timer to force the + // method to create a new timer for the new first long term item. + // + _nextLongTermWorkItem = null; + EvaluateLongTermQueue(); + } + } + } + + /// + /// Represents a work item in the absolute time scheduler. + /// + /// + /// This type is very similar to ScheduledItem, but we need a different Invoke signature to allow customization + /// of the target scheduler (e.g. when called in a recursive scheduling context, see ExecuteNextShortTermWorkItem). + /// + private abstract class WorkItem : IComparable, IDisposable + { + public readonly LocalScheduler Scheduler; + public readonly DateTimeOffset DueTime; + + private SingleAssignmentDisposableValue _disposable; + private int _hasRun; + + protected WorkItem(LocalScheduler scheduler, DateTimeOffset dueTime) + { + Scheduler = scheduler; + DueTime = dueTime; + + _hasRun = 0; + } + + public void Invoke(IScheduler scheduler) + { + // + // Protect against possible maltreatment of the scheduler queues or races in + // execution of a work item that got relocated across system clock changes. + // Under no circumstance whatsoever we should run work twice. The monitor's + // ref count should also be subject to this policy. + // + if (Interlocked.Exchange(ref _hasRun, 1) == 0) + { + try + { + if (!_disposable.IsDisposed) + { + _disposable.Disposable = InvokeCore(scheduler); + } + } + finally + { + SystemClock.Release(); + } + } + } + + protected abstract IDisposable InvokeCore(IScheduler scheduler); + + public int CompareTo(WorkItem? other) => DueTime.CompareTo(other!.DueTime); + + public void Dispose() => _disposable.Dispose(); + } + + /// + /// Represents a work item that closes over scheduler invocation state. Subtyping is + /// used to have a common type for the scheduler queues. + /// + private sealed class WorkItem : WorkItem + { + private readonly TState _state; + private readonly Func _action; + + public WorkItem(LocalScheduler scheduler, TState state, DateTimeOffset dueTime, Func action) + : base(scheduler, dueTime) + { + _state = state; + _action = action; + } + + protected override IDisposable InvokeCore(IScheduler scheduler) => _action(scheduler, _state); + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/LocalScheduler.cs b/LibExternal/System.Reactive/Concurrency/LocalScheduler.cs new file mode 100644 index 0000000..69ebb36 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/LocalScheduler.cs @@ -0,0 +1,103 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Concurrency +{ + /// + /// Abstract base class for machine-local schedulers, using the local system clock for time-based operations. + /// + public abstract partial class LocalScheduler : IScheduler, IStopwatchProvider, IServiceProvider + { + /// + /// Gets the scheduler's notion of current time. + /// + public virtual DateTimeOffset Now => Scheduler.Now; + + /// + /// Schedules an action to be executed. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public virtual IDisposable Schedule(TState state, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return Schedule(state, TimeSpan.Zero, action); + } + + /// + /// Schedules an action to be executed after dueTime. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// Relative time after which to execute the action. + /// The disposable object used to cancel the scheduled action (best effort). + public abstract IDisposable Schedule(TState state, TimeSpan dueTime, Func action); + + /// + /// Schedules an action to be executed at dueTime. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// Absolute time at which to execute the action. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public virtual IDisposable Schedule(TState state, DateTimeOffset dueTime, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return Enqueue(state, dueTime, action); + } + + /// + /// Starts a new stopwatch object. + /// + /// New stopwatch object; started at the time of the request. + /// + /// Platform-specific scheduler implementations should reimplement + /// to provide a more efficient implementation (if available). + /// + public virtual IStopwatch StartStopwatch() => ConcurrencyAbstractionLayer.Current.StartStopwatch(); + + object? IServiceProvider.GetService(Type serviceType) => GetService(serviceType); + + /// + /// Discovers scheduler services by interface type. The base class implementation returns + /// requested services for each scheduler interface implemented by the derived class. For + /// more control over service discovery, derived types can override this method. + /// + /// Scheduler service interface type to discover. + /// Object implementing the requested service, if available; null otherwise. + protected virtual object? GetService(Type serviceType) + { + if (serviceType == typeof(IStopwatchProvider)) + { + return this; + } + + if (serviceType == typeof(ISchedulerLongRunning)) + { + return this as ISchedulerLongRunning; + } + + if (serviceType == typeof(ISchedulerPeriodic)) + { + return this as ISchedulerPeriodic; + } + + return null; + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/NewThreadScheduler.cs b/LibExternal/System.Reactive/Concurrency/NewThreadScheduler.cs new file mode 100644 index 0000000..b589085 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/NewThreadScheduler.cs @@ -0,0 +1,194 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; +using System.Threading; + +namespace System.Reactive.Concurrency +{ + /// + /// Represents an object that schedules each unit of work on a separate thread. + /// + public sealed class NewThreadScheduler : LocalScheduler, ISchedulerLongRunning, ISchedulerPeriodic + { + private static readonly Lazy Instance = new(static () => new NewThreadScheduler()); + + private readonly Func _threadFactory; + + /// + /// Creates an object that schedules each unit of work on a separate thread. + /// + public NewThreadScheduler() + : this(action => new Thread(action)) + { + } + + /// + /// Gets an instance of this scheduler that uses the default Thread constructor. + /// + public static NewThreadScheduler Default => Instance.Value; + + /// + /// Creates an object that schedules each unit of work on a separate thread. + /// + /// Factory function for thread creation. + /// is null. + public NewThreadScheduler(Func threadFactory) + { + _threadFactory = threadFactory ?? throw new ArgumentNullException(nameof(threadFactory)); + } + + /// + /// Schedules an action to be executed after dueTime. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// Relative time after which to execute the action. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var scheduler = new EventLoopScheduler(_threadFactory) + { + ExitIfEmpty = true + }; + return scheduler.Schedule(state, dueTime, action); + } + + /// + /// Schedules a long-running task by creating a new thread. Cancellation happens through polling. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public IDisposable ScheduleLongRunning(TState state, Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var d = new BooleanDisposable(); + + var thread = _threadFactory(() => + { + // + // Notice we don't check d.IsDisposed. The contract for ISchedulerLongRunning + // requires us to ensure the scheduled work gets an opportunity to observe + // the cancellation request. + // + action(state, d); + }); + + thread.Start(); + + return d; + } + + /// + /// Schedules a periodic piece of work by creating a new thread that goes to sleep when work has been dispatched and wakes up again at the next periodic due time. + /// + /// The type of the state passed to the scheduled action. + /// Initial state passed to the action upon the first iteration. + /// Period for running the work periodically. + /// Action to be executed, potentially updating the state. + /// The disposable object used to cancel the scheduled recurring action (best effort). + /// is null. + /// is less than . + public IDisposable SchedulePeriodic(TState state, TimeSpan period, Func action) + { + if (period < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(period)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var periodic = new Periodic(state, period, action); + + var thread = _threadFactory(periodic.Run); + thread.Start(); + + return periodic; + } + + private sealed class Periodic : IDisposable + { + private readonly IStopwatch _stopwatch; + private readonly TimeSpan _period; + private readonly Func _action; + + private readonly object _cancel = new(); + private volatile bool _done; + + private TState _state; + private TimeSpan _next; + + public Periodic(TState state, TimeSpan period, Func action) + { + _stopwatch = ConcurrencyAbstractionLayer.Current.StartStopwatch(); + + _period = period; + _action = action; + + _state = state; + _next = period; + } + + public void Run() + { + while (!_done) + { + var timeout = Scheduler.Normalize(_next - _stopwatch.Elapsed); + + lock (_cancel) + { + if (Monitor.Wait(_cancel, timeout)) + { + return; + } + } + + _state = _action(_state); + _next += _period; + } + } + + public void Dispose() + { + _done = true; + + lock (_cancel) + { + Monitor.Pulse(_cancel); + } + } + } + + /// + /// Starts a new stopwatch object. + /// + /// New stopwatch object; started at the time of the request. + public override IStopwatch StartStopwatch() + { + // + // Strictly speaking, this explicit override is not necessary because the base implementation calls into + // the enlightenment module to obtain the CAL, which would circle back to System.Reactive.PlatformServices + // where we're currently running. This is merely a short-circuit to avoid the additional roundtrip. + // + return new StopwatchImpl(); + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/ScheduledItem.cs b/LibExternal/System.Reactive/Concurrency/ScheduledItem.cs new file mode 100644 index 0000000..50291f1 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/ScheduledItem.cs @@ -0,0 +1,209 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Disposables; + +namespace System.Reactive.Concurrency +{ + /// + /// Abstract base class for scheduled work items. + /// + /// Absolute time representation type. +#pragma warning disable CA1033, CA1063, CA1816 // (Overridable IDisposable.) This is a specialized base type, and it would be inappropriate to encourage anyone to build derived types that do more in Dispose. + public abstract class ScheduledItem : IScheduledItem, IComparable>, IDisposable + where TAbsolute : IComparable + { + private SingleAssignmentDisposableValue _disposable; + private readonly IComparer _comparer; + + /// + /// Creates a new scheduled work item to run at the specified time. + /// + /// Absolute time at which the work item has to be executed. + /// Comparer used to compare work items based on their scheduled time. + /// is null. + protected ScheduledItem(TAbsolute dueTime, IComparer comparer) + { + DueTime = dueTime; + _comparer = comparer ?? throw new ArgumentNullException(nameof(comparer)); + } + + /// + /// Gets the absolute time at which the item is due for invocation. + /// + public TAbsolute DueTime { get; } + + /// + /// Invokes the work item. + /// + public void Invoke() + { + if (!_disposable.IsDisposed) + { + _disposable.Disposable = InvokeCore(); + } + } + + /// + /// Implement this method to perform the work item invocation, returning a disposable object for deep cancellation. + /// + /// Disposable object used to cancel the work item and/or derived work items. + protected abstract IDisposable InvokeCore(); + + #region Inequality + + /// + /// Compares the work item with another work item based on absolute time values. + /// + /// Work item to compare the current work item to. + /// Relative ordering between this and the specified work item. + /// The inequality operators are overloaded to provide results consistent with the implementation. Equality operators implement traditional reference equality semantics. + public int CompareTo(ScheduledItem? other) + { + // MSDN: By definition, any object compares greater than null, and two null references compare equal to each other. + if (other is null) + { + return 1; + } + + return _comparer.Compare(DueTime, other.DueTime); + } + + /// + /// Determines whether one specified object is due before a second specified object. + /// + /// The first object to compare. + /// The second object to compare. + /// true if the value of left is earlier than the value of right; otherwise, false. + /// This operator provides results consistent with the implementation. + public static bool operator <(ScheduledItem left, ScheduledItem right) => Comparer>.Default.Compare(left, right) < 0; + + /// + /// Determines whether one specified object is due before or at the same of a second specified object. + /// + /// The first object to compare. + /// The second object to compare. + /// true if the value of left is earlier than or simultaneous with the value of right; otherwise, false. + /// This operator provides results consistent with the implementation. + public static bool operator <=(ScheduledItem left, ScheduledItem right) => Comparer>.Default.Compare(left, right) <= 0; + + /// + /// Determines whether one specified object is due after a second specified object. + /// + /// The first object to compare. + /// The second object to compare. + /// true if the value of left is later than the value of right; otherwise, false. + /// This operator provides results consistent with the implementation. + public static bool operator >(ScheduledItem left, ScheduledItem right) => Comparer>.Default.Compare(left, right) > 0; + + /// + /// Determines whether one specified object is due after or at the same time of a second specified object. + /// + /// The first object to compare. + /// The second object to compare. + /// true if the value of left is later than or simultaneous with the value of right; otherwise, false. + /// This operator provides results consistent with the implementation. + public static bool operator >=(ScheduledItem left, ScheduledItem right) => Comparer>.Default.Compare(left, right) >= 0; + + #endregion + + #region Equality + + /// + /// Determines whether two specified objects are equal. + /// + /// The first object to compare. + /// The second object to compare. + /// true if both are equal; otherwise, false. + /// This operator does not provide results consistent with the IComparable implementation. Instead, it implements reference equality. + public static bool operator ==(ScheduledItem? left, ScheduledItem? right) => ReferenceEquals(left, right); + + /// + /// Determines whether two specified objects are inequal. + /// + /// The first object to compare. + /// The second object to compare. + /// true if both are inequal; otherwise, false. + /// This operator does not provide results consistent with the IComparable implementation. Instead, it implements reference equality. + public static bool operator !=(ScheduledItem? left, ScheduledItem? right) => !(left == right); + + /// + /// Determines whether a object is equal to the specified object. + /// + /// The object to compare to the current object. + /// true if the obj parameter is a object and is equal to the current object; otherwise, false. + public override bool Equals(object? obj) => ReferenceEquals(this, obj); + + /// + /// Returns the hash code for the current object. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() => base.GetHashCode(); + + #endregion + + /// + /// Cancels the work item by disposing the resource returned by as soon as possible. + /// + public void Cancel() => _disposable.Dispose(); + + /// + /// Gets whether the work item has received a cancellation request. + /// + public bool IsCanceled => _disposable.IsDisposed; + + void IDisposable.Dispose() => Cancel(); + } +#pragma warning restore CA1033, CA1063, CA1816 + + /// + /// Represents a scheduled work item based on the materialization of an IScheduler.Schedule method call. + /// + /// Absolute time representation type. + /// Type of the state passed to the scheduled action. + public sealed class ScheduledItem : ScheduledItem + where TAbsolute : IComparable + { + private readonly IScheduler _scheduler; + private readonly TValue _state; + private readonly Func _action; + + /// + /// Creates a materialized work item. + /// + /// Recursive scheduler to invoke the scheduled action with. + /// State to pass to the scheduled action. + /// Scheduled action. + /// Time at which to run the scheduled action. + /// Comparer used to compare work items based on their scheduled time. + /// or or is null. + public ScheduledItem(IScheduler scheduler, TValue state, Func action, TAbsolute dueTime, IComparer comparer) + : base(dueTime, comparer) + { + _scheduler = scheduler ?? throw new ArgumentNullException(nameof(scheduler)); + _state = state; + _action = action ?? throw new ArgumentNullException(nameof(action)); + } + + /// + /// Creates a materialized work item. + /// + /// Recursive scheduler to invoke the scheduled action with. + /// State to pass to the scheduled action. + /// Scheduled action. + /// Time at which to run the scheduled action. + /// or is null. + public ScheduledItem(IScheduler scheduler, TValue state, Func action, TAbsolute dueTime) + : this(scheduler, state, action, dueTime, Comparer.Default) + { + } + + /// + /// Invokes the scheduled action with the supplied recursive scheduler and state. + /// + /// Cancellation resource returned by the scheduled action. + protected override IDisposable InvokeCore() => _action(_scheduler, _state); + } +} diff --git a/LibExternal/System.Reactive/Concurrency/Scheduler.Async.cs b/LibExternal/System.Reactive/Concurrency/Scheduler.Async.cs new file mode 100644 index 0000000..bc07404 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/Scheduler.Async.cs @@ -0,0 +1,517 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Reactive.Concurrency +{ + public static partial class Scheduler + { + private sealed class AsyncInvocation : IDisposable + { + private readonly CancellationTokenSource _cts = new(); + private SingleAssignmentDisposableValue _run; + + public IDisposable Run(IScheduler self, TState s, Func> action) + { + if (_cts.IsCancellationRequested) + return Disposable.Empty; + + action(new CancelableScheduler(self, _cts.Token), s, _cts.Token).ContinueWith( + static (t, thisObject) => + { + var @this = (AsyncInvocation)thisObject!; + + t.Exception?.Handle(static e => e is OperationCanceledException); + + @this._run.Disposable = t.Result; + }, + this, + TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.NotOnCanceled); + + return this; + } + + public void Dispose() + { + _cts.Cancel(); + _run.Dispose(); + } + } + + /// + /// Yields execution of the current work item on the scheduler to another work item on the scheduler. + /// The caller should await the result of calling Yield to schedule the remainder of the current work item (known as the continuation). + /// + /// Scheduler to yield work on. + /// Scheduler operation object to await in order to schedule the continuation. + /// is null. + public static SchedulerOperation Yield(this IScheduler scheduler) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return new SchedulerOperation(a => scheduler.Schedule(a), scheduler.GetCancellationToken()); + } + + /// + /// Yields execution of the current work item on the scheduler to another work item on the scheduler. + /// The caller should await the result of calling Yield to schedule the remainder of the current work item (known as the continuation). + /// + /// Scheduler to yield work on. + /// Cancellation token to cancel the continuation to run. + /// Scheduler operation object to await in order to schedule the continuation. + /// is null. + public static SchedulerOperation Yield(this IScheduler scheduler, CancellationToken cancellationToken) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return new SchedulerOperation(a => scheduler.Schedule(a), cancellationToken); + } + + /// + /// Suspends execution of the current work item on the scheduler for the specified duration. + /// The caller should await the result of calling Sleep to schedule the remainder of the current work item (known as the continuation) after the specified duration. + /// + /// Scheduler to yield work on. + /// Time when the continuation should run. + /// Scheduler operation object to await in order to schedule the continuation. + /// is null. + public static SchedulerOperation Sleep(this IScheduler scheduler, TimeSpan dueTime) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return new SchedulerOperation(a => scheduler.Schedule(dueTime, a), scheduler.GetCancellationToken()); + } + + /// + /// Suspends execution of the current work item on the scheduler for the specified duration. + /// The caller should await the result of calling Sleep to schedule the remainder of the current work item (known as the continuation) after the specified duration. + /// + /// Scheduler to yield work on. + /// Time when the continuation should run. + /// Cancellation token to cancel the continuation to run. + /// Scheduler operation object to await in order to schedule the continuation. + /// is null. + public static SchedulerOperation Sleep(this IScheduler scheduler, TimeSpan dueTime, CancellationToken cancellationToken) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return new SchedulerOperation(a => scheduler.Schedule(dueTime, a), cancellationToken); + } + + /// + /// Suspends execution of the current work item on the scheduler until the specified due time. + /// The caller should await the result of calling Sleep to schedule the remainder of the current work item (known as the continuation) at the specified due time. + /// + /// Scheduler to yield work on. + /// Time when the continuation should run. + /// Scheduler operation object to await in order to schedule the continuation. + /// is null. + public static SchedulerOperation Sleep(this IScheduler scheduler, DateTimeOffset dueTime) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return new SchedulerOperation(a => scheduler.Schedule(dueTime, a), scheduler.GetCancellationToken()); + } + + /// + /// Suspends execution of the current work item on the scheduler until the specified due time. + /// The caller should await the result of calling Sleep to schedule the remainder of the current work item (known as the continuation) at the specified due time. + /// + /// Scheduler to yield work on. + /// Time when the continuation should run. + /// Cancellation token to cancel the continuation to run. + /// Scheduler operation object to await in order to schedule the continuation. + /// is null. + public static SchedulerOperation Sleep(this IScheduler scheduler, DateTimeOffset dueTime, CancellationToken cancellationToken) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return new SchedulerOperation(a => scheduler.Schedule(dueTime, a), cancellationToken); + } + + /// + /// Schedules work using an asynchronous method, allowing for cooperative scheduling in an imperative coding style. + /// + /// The type of the state passed to the scheduled action. + /// Scheduler to schedule work on. + /// State to pass to the asynchronous method. + /// Asynchronous method to run the work, using Yield and Sleep operations for cooperative scheduling and injection of cancellation points. + /// Disposable object that allows to cancel outstanding work on cooperative cancellation points or through the cancellation token passed to the asynchronous method. + /// or is null. + public static IDisposable ScheduleAsync(this IScheduler scheduler, TState state, Func action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return ScheduleAsync_(scheduler, state, action); + } + + /// + /// Schedules work using an asynchronous method, allowing for cooperative scheduling in an imperative coding style. + /// + /// The type of the state passed to the scheduled action. + /// Scheduler to schedule work on. + /// State to pass to the asynchronous method. + /// Asynchronous method to run the work, using Yield and Sleep operations for cooperative scheduling and injection of cancellation points. + /// Disposable object that allows to cancel outstanding work on cooperative cancellation points or through the cancellation token passed to the asynchronous method. + /// or is null. + public static IDisposable ScheduleAsync(this IScheduler scheduler, TState state, Func> action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return ScheduleAsync_(scheduler, state, action); + } + + /// + /// Schedules work using an asynchronous method, allowing for cooperative scheduling in an imperative coding style. + /// + /// Scheduler to schedule work on. + /// Asynchronous method to run the work, using Yield and Sleep operations for cooperative scheduling and injection of cancellation points. + /// Disposable object that allows to cancel outstanding work on cooperative cancellation points or through the cancellation token passed to the asynchronous method. + /// or is null. + public static IDisposable ScheduleAsync(this IScheduler scheduler, Func action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return ScheduleAsync_(scheduler, action, static (self, closureAction, ct) => closureAction(self, ct)); + } + + /// + /// Schedules work using an asynchronous method, allowing for cooperative scheduling in an imperative coding style. + /// + /// Scheduler to schedule work on. + /// Asynchronous method to run the work, using Yield and Sleep operations for cooperative scheduling and injection of cancellation points. + /// Disposable object that allows to cancel outstanding work on cooperative cancellation points or through the cancellation token passed to the asynchronous method. + /// or is null. + public static IDisposable ScheduleAsync(this IScheduler scheduler, Func> action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return ScheduleAsync_(scheduler, action, static (self, closureAction, ct) => closureAction(self, ct)); + } + + /// + /// Schedules work using an asynchronous method, allowing for cooperative scheduling in an imperative coding style. + /// + /// The type of the state passed to the scheduled action. + /// Scheduler to schedule work on. + /// State to pass to the asynchronous method. + /// Relative time after which to execute the action. + /// Asynchronous method to run the work, using Yield and Sleep operations for cooperative scheduling and injection of cancellation points. + /// Disposable object that allows to cancel outstanding work on cooperative cancellation points or through the cancellation token passed to the asynchronous method. + /// or is null. + public static IDisposable ScheduleAsync(this IScheduler scheduler, TState state, TimeSpan dueTime, Func action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return ScheduleAsync_(scheduler, state, dueTime, action); + } + + /// + /// Schedules work using an asynchronous method, allowing for cooperative scheduling in an imperative coding style. + /// + /// The type of the state passed to the scheduled action. + /// Scheduler to schedule work on. + /// State to pass to the asynchronous method. + /// Relative time after which to execute the action. + /// Asynchronous method to run the work, using Yield and Sleep operations for cooperative scheduling and injection of cancellation points. + /// Disposable object that allows to cancel outstanding work on cooperative cancellation points or through the cancellation token passed to the asynchronous method. + /// or is null. + public static IDisposable ScheduleAsync(this IScheduler scheduler, TState state, TimeSpan dueTime, Func> action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return ScheduleAsync_(scheduler, state, dueTime, action); + } + + /// + /// Schedules work using an asynchronous method, allowing for cooperative scheduling in an imperative coding style. + /// + /// Scheduler to schedule work on. + /// Relative time after which to execute the action. + /// Asynchronous method to run the work, using Yield and Sleep operations for cooperative scheduling and injection of cancellation points. + /// Disposable object that allows to cancel outstanding work on cooperative cancellation points or through the cancellation token passed to the asynchronous method. + /// or is null. + public static IDisposable ScheduleAsync(this IScheduler scheduler, TimeSpan dueTime, Func action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return ScheduleAsync_(scheduler, action, dueTime, static (self, closureAction, ct) => closureAction(self, ct)); + } + + /// + /// Schedules work using an asynchronous method, allowing for cooperative scheduling in an imperative coding style. + /// + /// Scheduler to schedule work on. + /// Relative time after which to execute the action. + /// Asynchronous method to run the work, using Yield and Sleep operations for cooperative scheduling and injection of cancellation points. + /// Disposable object that allows to cancel outstanding work on cooperative cancellation points or through the cancellation token passed to the asynchronous method. + /// or is null. + public static IDisposable ScheduleAsync(this IScheduler scheduler, TimeSpan dueTime, Func> action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return ScheduleAsync_(scheduler, action, dueTime, static (self, closureAction, ct) => closureAction(self, ct)); + } + + /// + /// Schedules work using an asynchronous method, allowing for cooperative scheduling in an imperative coding style. + /// + /// The type of the state passed to the scheduled action. + /// Scheduler to schedule work on. + /// State to pass to the asynchronous method. + /// Absolute time at which to execute the action. + /// Asynchronous method to run the work, using Yield and Sleep operations for cooperative scheduling and injection of cancellation points. + /// Disposable object that allows to cancel outstanding work on cooperative cancellation points or through the cancellation token passed to the asynchronous method. + /// or is null. + public static IDisposable ScheduleAsync(this IScheduler scheduler, TState state, DateTimeOffset dueTime, Func action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return ScheduleAsync_(scheduler, state, dueTime, action); + } + + /// + /// Schedules work using an asynchronous method, allowing for cooperative scheduling in an imperative coding style. + /// + /// The type of the state passed to the scheduled action. + /// Scheduler to schedule work on. + /// State to pass to the asynchronous method. + /// Absolute time at which to execute the action. + /// Asynchronous method to run the work, using Yield and Sleep operations for cooperative scheduling and injection of cancellation points. + /// Disposable object that allows to cancel outstanding work on cooperative cancellation points or through the cancellation token passed to the asynchronous method. + /// or is null. + public static IDisposable ScheduleAsync(this IScheduler scheduler, TState state, DateTimeOffset dueTime, Func> action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return ScheduleAsync_(scheduler, state, dueTime, action); + } + + /// + /// Schedules work using an asynchronous method, allowing for cooperative scheduling in an imperative coding style. + /// + /// Scheduler to schedule work on. + /// Absolute time at which to execute the action. + /// Asynchronous method to run the work, using Yield and Sleep operations for cooperative scheduling and injection of cancellation points. + /// Disposable object that allows to cancel outstanding work on cooperative cancellation points or through the cancellation token passed to the asynchronous method. + /// or is null. + public static IDisposable ScheduleAsync(this IScheduler scheduler, DateTimeOffset dueTime, Func action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return ScheduleAsync_(scheduler, action, dueTime, static (self, closureAction, ct) => closureAction(self, ct)); + } + + /// + /// Schedules work using an asynchronous method, allowing for cooperative scheduling in an imperative coding style. + /// + /// Scheduler to schedule work on. + /// Absolute time at which to execute the action. + /// Asynchronous method to run the work, using Yield and Sleep operations for cooperative scheduling and injection of cancellation points. + /// Disposable object that allows to cancel outstanding work on cooperative cancellation points or through the cancellation token passed to the asynchronous method. + /// or is null. + public static IDisposable ScheduleAsync(this IScheduler scheduler, DateTimeOffset dueTime, Func> action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return ScheduleAsync_(scheduler, action, dueTime, static (self, closureAction, ct) => closureAction(self, ct)); + } + + private static IDisposable ScheduleAsync_(IScheduler scheduler, TState state, Func action) + { + return scheduler.Schedule((state, action), static (self, t) => InvokeAsync(self, t.state, t.action)); + } + + private static IDisposable ScheduleAsync_(IScheduler scheduler, TState state, Func> action) + { + return scheduler.Schedule((state, action), static (self, t) => InvokeAsync(self, t.state, t.action)); + } + + private static IDisposable ScheduleAsync_(IScheduler scheduler, TState state, TimeSpan dueTime, Func action) + { + return scheduler.Schedule((state, action), dueTime, static (self, t) => InvokeAsync(self, t.state, t.action)); + } + + private static IDisposable ScheduleAsync_(IScheduler scheduler, TState state, TimeSpan dueTime, Func> action) + { + return scheduler.Schedule((state, action), dueTime, static (self, t) => InvokeAsync(self, t.state, t.action)); + } + + private static IDisposable ScheduleAsync_(IScheduler scheduler, TState state, DateTimeOffset dueTime, Func action) + { + return scheduler.Schedule((state, action), dueTime, static (self, t) => InvokeAsync(self, t.state, t.action)); + } + + private static IDisposable ScheduleAsync_(IScheduler scheduler, TState state, DateTimeOffset dueTime, Func> action) + { + return scheduler.Schedule((state, action), dueTime, static (self, t) => InvokeAsync(self, t.state, t.action)); + } + + private static IDisposable InvokeAsync(IScheduler self, TState s, Func> action) + { + return new AsyncInvocation().Run(self, s, action); + } + + private static IDisposable InvokeAsync(IScheduler self, TState s, Func action) + { + return InvokeAsync(self, (action, state: s), static (self_, t, ct) => t.action(self_, t.state, ct).ContinueWith(static _ => Disposable.Empty)); + } + + private static CancellationToken GetCancellationToken(this IScheduler scheduler) + { + return scheduler is CancelableScheduler cs ? cs.Token : CancellationToken.None; + } + + private sealed class CancelableScheduler : IScheduler + { + private readonly IScheduler _scheduler; + private readonly CancellationToken _cancellationToken; + + public CancelableScheduler(IScheduler scheduler, CancellationToken cancellationToken) + { + _scheduler = scheduler; + _cancellationToken = cancellationToken; + } + + public CancellationToken Token + { + get { return _cancellationToken; } + } + + public DateTimeOffset Now => _scheduler.Now; + + public IDisposable Schedule(TState state, Func action) + { + return _scheduler.Schedule(state, action); + } + + public IDisposable Schedule(TState state, TimeSpan dueTime, Func action) + { + return _scheduler.Schedule(state, dueTime, action); + } + + public IDisposable Schedule(TState state, DateTimeOffset dueTime, Func action) + { + return _scheduler.Schedule(state, dueTime, action); + } + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/Scheduler.Recursive.cs b/LibExternal/System.Reactive/Concurrency/Scheduler.Recursive.cs new file mode 100644 index 0000000..6850bda --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/Scheduler.Recursive.cs @@ -0,0 +1,278 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; + +namespace System.Reactive.Concurrency +{ + public static partial class Scheduler + { + /// + /// Schedules an action to be executed recursively. + /// + /// Scheduler to execute the recursive action on. + /// Action to execute recursively. The parameter passed to the action is used to trigger recursive scheduling of the action. + /// The disposable object used to cancel the scheduled action (best effort). + /// or is null. + public static IDisposable Schedule(this IScheduler scheduler, Action action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return scheduler.Schedule(action, static (a, self) => a(() => self(a))); + } + + /// + /// Schedules an action to be executed recursively. + /// + /// The type of the state passed to the scheduled action. + /// Scheduler to execute the recursive action on. + /// State passed to the action to be executed. + /// Action to execute recursively. The last parameter passed to the action is used to trigger recursive scheduling of the action, passing in recursive invocation state. + /// The disposable object used to cancel the scheduled action (best effort). + /// or is null. + public static IDisposable Schedule(this IScheduler scheduler, TState state, Action> action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return scheduler.Schedule((state, action), static (s, p) => InvokeRec1(s, p)); + } + + private static IDisposable InvokeRec1(IScheduler scheduler, (TState state, Action> action) tuple) + { + var recursiveInvoker = new InvokeRec1State(scheduler, tuple.action); + recursiveInvoker.InvokeFirst(tuple.state); + return recursiveInvoker; + } + + /// + /// Schedules an action to be executed recursively after a specified relative due time. + /// + /// Scheduler to execute the recursive action on. + /// Action to execute recursively. The parameter passed to the action is used to trigger recursive scheduling of the action at the specified relative time. + /// Relative time after which to execute the action for the first time. + /// The disposable object used to cancel the scheduled action (best effort). + /// or is null. + public static IDisposable Schedule(this IScheduler scheduler, TimeSpan dueTime, Action> action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return scheduler.Schedule(action, dueTime, static (a, self) => a(ts => self(a, ts))); + } + + /// + /// Schedules an action to be executed recursively after a specified relative due time. + /// + /// The type of the state passed to the scheduled action. + /// Scheduler to execute the recursive action on. + /// State passed to the action to be executed. + /// Action to execute recursively. The last parameter passed to the action is used to trigger recursive scheduling of the action, passing in the recursive due time and invocation state. + /// Relative time after which to execute the action for the first time. + /// The disposable object used to cancel the scheduled action (best effort). + /// or is null. + public static IDisposable Schedule(this IScheduler scheduler, TState state, TimeSpan dueTime, Action> action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return scheduler.Schedule((state, action), dueTime, static (s, p) => InvokeRec2(s, p)); + } + + private static IDisposable InvokeRec2(IScheduler scheduler, (TState state, Action> action) tuple) + { + var recursiveInvoker = new InvokeRec2State(scheduler, tuple.action); + recursiveInvoker.InvokeFirst(tuple.state); + return recursiveInvoker; + } + + /// + /// Schedules an action to be executed recursively at a specified absolute due time. + /// + /// Scheduler to execute the recursive action on. + /// Action to execute recursively. The parameter passed to the action is used to trigger recursive scheduling of the action at the specified absolute time. + /// Absolute time at which to execute the action for the first time. + /// The disposable object used to cancel the scheduled action (best effort). + /// or is null. + public static IDisposable Schedule(this IScheduler scheduler, DateTimeOffset dueTime, Action> action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return scheduler.Schedule(action, dueTime, static (a, self) => a(dt => self(a, dt))); + } + + /// + /// Schedules an action to be executed recursively at a specified absolute due time. + /// + /// The type of the state passed to the scheduled action. + /// Scheduler to execute the recursive action on. + /// State passed to the action to be executed. + /// Action to execute recursively. The last parameter passed to the action is used to trigger recursive scheduling of the action, passing in the recursive due time and invocation state. + /// Absolute time at which to execute the action for the first time. + /// The disposable object used to cancel the scheduled action (best effort). + /// or is null. + public static IDisposable Schedule(this IScheduler scheduler, TState state, DateTimeOffset dueTime, Action> action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return scheduler.Schedule((state, action), dueTime, static (s, p) => InvokeRec3(s, p)); + } + + private static IDisposable InvokeRec3(IScheduler scheduler, (TState state, Action> action) tuple) + { + var recursiveInvoker = new InvokeRec3State(scheduler, tuple.action); + recursiveInvoker.InvokeFirst(tuple.state); + return recursiveInvoker; + } + + private abstract class InvokeRecBaseState : IDisposable + { + protected readonly IScheduler Scheduler; + + protected readonly CompositeDisposable Group; + + protected InvokeRecBaseState(IScheduler scheduler) + { + Scheduler = scheduler; + Group = new CompositeDisposable(); + } + + public void Dispose() + { + Group.Dispose(); + } + + } + + private sealed class InvokeRec1State : InvokeRecBaseState + { + private readonly Action> _action; + private readonly Action _recurseCallback; + + public InvokeRec1State(IScheduler scheduler, Action> action) : base(scheduler) + { + _action = action; + _recurseCallback = state => InvokeNext(state); + } + + private void InvokeNext(TState state) + { + var sad = new SingleAssignmentDisposable(); + + Group.Add(sad); + sad.Disposable = Scheduler.ScheduleAction((state, sad, @this: this), static nextState => + { + nextState.@this.Group.Remove(nextState.sad); + nextState.@this.InvokeFirst(nextState.state); + }); + } + + internal void InvokeFirst(TState state) + { + _action(state, _recurseCallback); + } + } + + private sealed class InvokeRec2State : InvokeRecBaseState + { + private readonly Action> _action; + private readonly Action _recurseCallback; + + public InvokeRec2State(IScheduler scheduler, Action> action) : base(scheduler) + { + _action = action; + _recurseCallback = (state, time) => InvokeNext(state, time); + } + + private void InvokeNext(TState state, TimeSpan time) + { + var sad = new SingleAssignmentDisposable(); + + Group.Add(sad); + sad.Disposable = Scheduler.ScheduleAction((state, sad, @this: this), time, static nextState => { + nextState.@this.Group.Remove(nextState.sad); + nextState.@this.InvokeFirst(nextState.state); + }); + } + + internal void InvokeFirst(TState state) + { + _action(state, _recurseCallback); + } + } + + private sealed class InvokeRec3State : InvokeRecBaseState + { + private readonly Action> _action; + private readonly Action _recurseCallback; + + public InvokeRec3State(IScheduler scheduler, Action> action) : base(scheduler) + { + _action = action; + _recurseCallback = (state, dtOffset) => InvokeNext(state, dtOffset); + } + + private void InvokeNext(TState state, DateTimeOffset dtOffset) + { + var sad = new SingleAssignmentDisposable(); + + Group.Add(sad); + sad.Disposable = Scheduler.ScheduleAction((state, sad, @this: this), dtOffset, static nextState => { + nextState.@this.Group.Remove(nextState.sad); + nextState.@this.InvokeFirst(nextState.state); + }); + } + + internal void InvokeFirst(TState state) + { + _action(state, _recurseCallback); + } + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/Scheduler.Services.Emulation.cs b/LibExternal/System.Reactive/Concurrency/Scheduler.Services.Emulation.cs new file mode 100644 index 0000000..582f45c --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/Scheduler.Services.Emulation.cs @@ -0,0 +1,667 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Reactive.Disposables; +using System.Reactive.PlatformServices; +using System.Threading; + +namespace System.Reactive.Concurrency +{ + public static partial class Scheduler + { + /// + /// Schedules a periodic piece of work by dynamically discovering the scheduler's capabilities. + /// If the scheduler supports periodic scheduling, the request will be forwarded to the periodic scheduling implementation. + /// If the scheduler provides stopwatch functionality, the periodic task will be emulated using recursive scheduling with a stopwatch to correct for time slippage. + /// Otherwise, the periodic task will be emulated using recursive scheduling. + /// + /// The type of the state passed to the scheduled action. + /// The scheduler to run periodic work on. + /// Initial state passed to the action upon the first iteration. + /// Period for running the work periodically. + /// Action to be executed, potentially updating the state. + /// The disposable object used to cancel the scheduled recurring action (best effort). + /// or is null. + /// is less than . + public static IDisposable SchedulePeriodic(this IScheduler scheduler, TState state, TimeSpan period, Func action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (period < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(period)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return SchedulePeriodic_(scheduler, state, period, action); + } + + /// + /// Schedules a periodic piece of work by dynamically discovering the scheduler's capabilities. + /// If the scheduler supports periodic scheduling, the request will be forwarded to the periodic scheduling implementation. + /// If the scheduler provides stopwatch functionality, the periodic task will be emulated using recursive scheduling with a stopwatch to correct for time slippage. + /// Otherwise, the periodic task will be emulated using recursive scheduling. + /// + /// The type of the state passed to the scheduled action. + /// Scheduler to execute the action on. + /// State passed to the action to be executed. + /// Period for running the work periodically. + /// Action to be executed. + /// The disposable object used to cancel the scheduled recurring action (best effort). + /// or is null. + /// is less than . + public static IDisposable SchedulePeriodic(this IScheduler scheduler, TState state, TimeSpan period, Action action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (period < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(period)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return SchedulePeriodic_(scheduler, (state, action), period, static t => { t.action(t.state); return t; }); + } + + /// + /// Schedules a periodic piece of work by dynamically discovering the scheduler's capabilities. + /// If the scheduler supports periodic scheduling, the request will be forwarded to the periodic scheduling implementation. + /// If the scheduler provides stopwatch functionality, the periodic task will be emulated using recursive scheduling with a stopwatch to correct for time slippage. + /// Otherwise, the periodic task will be emulated using recursive scheduling. + /// + /// Scheduler to execute the action on. + /// Period for running the work periodically. + /// Action to be executed. + /// The disposable object used to cancel the scheduled recurring action (best effort). + /// or is null. + /// is less than . + public static IDisposable SchedulePeriodic(this IScheduler scheduler, TimeSpan period, Action action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (period < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(period)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return SchedulePeriodic_(scheduler, action, period, static a => { a(); return a; }); + } + + /// + /// Starts a new stopwatch object by dynamically discovering the scheduler's capabilities. + /// If the scheduler provides stopwatch functionality, the request will be forwarded to the stopwatch provider implementation. + /// Otherwise, the stopwatch will be emulated using the scheduler's notion of absolute time. + /// + /// Scheduler to obtain a stopwatch for. + /// New stopwatch object; started at the time of the request. + /// is null. + /// The resulting stopwatch object can have non-monotonic behavior. + public static IStopwatch StartStopwatch(this IScheduler scheduler) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + // + // All schedulers deriving from LocalScheduler will automatically pick up this + // capability based on a local stopwatch, typically using QueryPerformanceCounter + // through the System.Diagnostics.Stopwatch class. + // + // Notice virtual time schedulers do implement this facility starting from Rx v2.0, + // using subtraction of their absolute time notion to compute elapsed time values. + // This is fine because those schedulers do not allow the clock to go back in time. + // + // For schedulers that don't have a stopwatch, we have to pick some fallback logic + // here. We could either dismiss the scheduler's notion of time and go for the CAL's + // stopwatch facility, or go with a stopwatch based on "scheduler.Now", which has + // the drawback of potentially going back in time: + // + // - Using the CAL's stopwatch facility causes us to abandon the scheduler's + // potentially virtualized notion of time, always going for the local system + // time instead. + // + // - Using the scheduler's Now property for calculations can break monotonicity, + // and there's no right answer on how to deal with jumps back in time. + // + // However, even the built-in stopwatch in the BCL can potentially fall back to + // subtraction of DateTime values in case no high-resolution performance counter is + // available, causing monotonicity to break down. We're not trying to solve this + // problem there either (though we could check IsHighResolution and smoothen out + // non-monotonic points somehow), so we pick the latter option as the lesser of + // two evils (also because it should occur rarely). + // + // Users of the stopwatch retrieved by this method could detect nonsensical data + // revealing a jump back in time, or implement custom fallback logic like the one + // shown below. + // + var swp = scheduler.AsStopwatchProvider(); + if (swp != null) + { + return swp.StartStopwatch(); + } + + return new EmulatedStopwatch(scheduler); + } + + private static IDisposable SchedulePeriodic_(IScheduler scheduler, TState state, TimeSpan period, Func action) + { + // + // Design rationale: + // + // In Rx v1.x, we employed recursive scheduling for periodic tasks. The following code + // fragment shows how the Timer (and hence Interval) function used to be implemented: + // + // var p = Normalize(period); + // + // return new AnonymousObservable(observer => + // { + // var d = dueTime; + // long count = 0; + // return scheduler.Schedule(d, self => + // { + // if (p > TimeSpan.Zero) + // { + // var now = scheduler.Now; + // d = d + p; + // if (d <= now) + // d = now + p; + // } + // + // observer.OnNext(count); + // count = unchecked(count + 1); + // self(d); + // }); + // }); + // + // Despite the purity of this approach, it suffered from a set of drawbacks: + // + // 1) Usage of IScheduler.Now to correct for time drift did have a positive effect for + // a limited number of scenarios, in particular when a short period was used. The + // major issues with this are: + // + // a) Relying on absolute time at the LINQ layer in Rx's layer map, causing issues + // when the system clock changes. Various customers hit this issue, reported to + // us on the MSDN forums. Basically, when the clock goes forward, the recursive + // loop wants to catch up as quickly as it can; when it goes backwards, a long + // silence will occur. (See 2 for a discussion of WP7 related fixes.) + // + // b) Even if a) would be addressed by using Rx v2.0's capabilities to monitor for + // system clock changes, the solution would violate the reasonable expectation + // of operators overloads using TimeSpan *not* relying on absolute time. + // + // c) Drift correction doesn't work for large periods when the system encounters + // systematic drift. For example, in the lab we've seen cases of drift up to + // tens of seconds on a 24 hour timeframe. Correcting for this drift by making + // a recursive call with a due time of 24 * 3600 with 10 seconds of adjustment + // won't fix systematic drift. + // + // 2) This implementation has been plagued with issues around application container + // lifecycle models, in particular Windows Phone 7's model of tombstoning and in + // particular its "dormant state". This feature was introduced in Mango to enable + // fast application switching. Essentially, the phone's OS puts the application + // in a suspended state when the user navigates "forward" (or takes an incoming + // call for instance). When the application is woken up again, threads are resumed + // and we're faced with an illusion of missed events due to the use of absolute + // time, not relative to how the application observes it. This caused nightmare + // scenarios of fast battery drain due to the flood of catch-up work. + // + // See http://msdn.microsoft.com/en-us/library/ff817008(v=vs.92).aspx for more + // information on this. + // + // 3) Recursive scheduling imposes a non-trivial cost due to the creation of many + // single-shot timers and closures. For high frequency timers, this can cause a + // lot of churn in the GC, which we like to avoid (operators shouldn't have hidden + // linear - or worse - allocation cost). + // + // Notice these drawbacks weren't limited to the use of Timer and Interval directly, + // as many operators such as Sample, Buffer, and Window used such sequences for their + // periodic behavior (typically by delegating to a more general overload). + // + // As a result, in Rx v2.0, we took the decision to improve periodic timing based on + // the following design decisions: + // + // 1) When the scheduler has the ability to run a periodic task, it should implement + // the ISchedulerPeriodic interface and expose it through the IServiceProvider + // interface. Passing the intent of the user through all layers of Rx, down to the + // underlying infrastructure provides delegation of responsibilities. This allows + // the target scheduler to optimize execution in various ways, e.g. by employing + // techniques such as timer coalescing. + // + // See http://www.bing.com/search?q=windows+timer+coalescing for information on + // techniques like timer coalescing which may be applied more aggressively in + // future OS releases in order to reduce power consumption. + // + // 2) Emulation of periodic scheduling is used to avoid breaking existing code that + // uses schedulers without this capability. We expect those fallback paths to be + // exercised rarely, though the use of DisableOptimizations can trigger them as + // well. In such cases we rely on stopwatches or a carefully crafted recursive + // scheme to deal with (or maximally compensate for) slippage or time. Behavior + // of periodic tasks is expected to be as follows: + // + // timer ticks 0-------1-------2-------3-------4-------5-------6----... + // | | | +====+ +==+ | | + // user code +~~~| +~| +~~~~~~~~~~~|+~~~~|+~~| +~~~| +~~| + // + // rather than the following scheme, where time slippage is introduced by user + // code running on the scheduler: + // + // timer ticks 0####-------1##-------2############-------3#####-----... + // | | | | + // user code +~~~| +~| +~~~~~~~~~~~| +~~~~| + // + // (Side-note: Unfortunately, we didn't reserve the name Interval for the latter + // behavior, but used it as an alias for "periodic scheduling" with + // the former behavior, delegating to the Timer implementation. One + // can simulate this behavior using Generate, which uses tail calls.) + // + // This behavior is important for operations like Sample, Buffer, and Window, all + // of which expect proper spacing of events, even if the user code takes a long + // time to complete (considered a bad practice nonetheless, cf. ObserveOn). + // + // 3) To deal with the issue of suspensions induced by application lifecycle events + // in Windows Phone and WinRT applications, we decided to hook available system + // events through IHostLifecycleNotifications, discovered through the PEP in order + // to maintain portability of the core of Rx. + // + + var periodic = scheduler.AsPeriodic(); +#if WINDOWS + // Workaround for WinRT not supporting <1ms resolution + if(period < TimeSpan.FromMilliseconds(1)) + { + periodic = null; // skip the periodic scheduler and use the stopwatch + } +#endif + if (periodic != null) + { + return periodic.SchedulePeriodic(state, period, action); + } + + var swp = scheduler.AsStopwatchProvider(); + if (swp != null) + { + var spr = new SchedulePeriodicStopwatch(scheduler, state, period, action, swp); + return spr.Start(); + } + else + { + var spr = new SchedulePeriodicRecursive(scheduler, state, period, action); + return spr.Start(); + } + } + + private sealed class SchedulePeriodicStopwatch : IDisposable + { + private readonly IScheduler _scheduler; + private readonly TimeSpan _period; + private readonly Func _action; + private readonly IStopwatchProvider _stopwatchProvider; + + public SchedulePeriodicStopwatch(IScheduler scheduler, TState state, TimeSpan period, Func action, IStopwatchProvider stopwatchProvider) + { + _scheduler = scheduler; + _period = period; + _action = action; + _stopwatchProvider = stopwatchProvider; + + _state = state; + _runState = Stopped; + } + + private TState _state; + + private readonly object _gate = new(); + private readonly AutoResetEvent _resumeEvent = new(false); + private volatile int _runState; + private IStopwatch? _stopwatch; + private TimeSpan _nextDue; + private TimeSpan _suspendedAt; + private TimeSpan _inactiveTime; + + // + // State transition diagram: + // (c) + // +-----------<-----------+ + // / \ + // / (b) \ + // | +-->--SUSPENDED---+ + // (a) v / | + // ^----STOPPED -->-- RUNNING -->--+ v (e) + // \ | + // +-->--DISPOSED----$ + // (d) + // + // (a) Start --> call to Schedule the Tick method + // (b) Suspending event handler --> Tick gets blocked waiting for _resumeEvent + // (c) Resuming event handler --> _resumeEvent is signaled, Tick continues + // (d) Dispose returned object from Start --> scheduled work is cancelled + // (e) Dispose returned object from Start --> unblocks _resumeEvent, Tick exits + // + private const int Stopped = 0; + private const int Running = 1; + private const int Suspended = 2; + private const int Disposed = 3; + + private SingleAssignmentDisposableValue _task; + + public IDisposable Start() + { + RegisterHostLifecycleEventHandlers(); + + _stopwatch = _stopwatchProvider.StartStopwatch(); + _nextDue = _period; + _runState = Running; + + _task.Disposable = _scheduler.Schedule(this, _nextDue, static (@this, a) => @this.Tick(a)); + return this; + } + + void IDisposable.Dispose() + { + _task.Dispose(); + Cancel(); + } + + private void Tick(Action, TimeSpan> recurse) + { + _nextDue += _period; + _state = _action(_state); + + var next = default(TimeSpan); + + while (true) + { + lock (_gate) + { + if (_runState == Running) + { + // + // This is the fast path. We just let the stopwatch continue to + // run while we're suspended, but compensate for time that was + // recorded as inactive based on cumulative deltas computed in + // the suspend and resume event handlers. + // + next = Normalize(_nextDue - (_stopwatch!.Elapsed - _inactiveTime)); + break; + } + + if (_runState == Disposed) + { + // + // In case the periodic job gets disposed but we are currently + // waiting to come back out of suspension, we should make sure + // we don't remain blocked indefinitely. Hence, we set the event + // in the Cancel method and trap this case here to bail out from + // the scheduled work gracefully. + // + return; + } + + // + // This is the least common case where we got suspended and need + // to block such that future reevaluations of the next due time + // will pick up the cumulative inactive time delta. + // + Debug.Assert(_runState == Suspended); + } + + // + // Only happens in the SUSPENDED case; otherwise we will have broken from + // the loop or have quit the Tick method. After returning from the wait, + // we'll either be RUNNING again, quit due to a DISPOSED transition, or + // be extremely unlucky to find ourselves SUSPENDED again and be blocked + // once more. + // + _resumeEvent.WaitOne(); + } + + recurse(this, next); + } + + private void Cancel() + { + UnregisterHostLifecycleEventHandlers(); + + lock (_gate) + { + _runState = Disposed; + + if (!Environment.HasShutdownStarted) + { + _resumeEvent.Set(); + } + } + } + + private void Suspending(object? sender, HostSuspendingEventArgs args) + { + // + // The host is telling us we're about to be suspended. At this point, time + // computations will still be in a valid range (next <= _period), but after + // we're woken up again, Tick would start to go on a crusade to catch up. + // + // This has caused problems in the past, where the flood of events caused + // batteries to drain etc (see design rationale discussion higher up). + // + // In order to mitigate this problem, we force Tick to suspend before its + // next computation of the next due time. Notice we can't afford to block + // during the Suspending event handler; the host expects us to respond to + // this event quickly, such that we're not keeping the application from + // suspending promptly. + // + lock (_gate) + { + if (_runState == Running) + { + _suspendedAt = _stopwatch!.Elapsed; // NB: Non-null when >= Running. + _runState = Suspended; + + if (!Environment.HasShutdownStarted) + { + _resumeEvent.Reset(); + } + } + } + } + + private void Resuming(object? sender, HostResumingEventArgs args) + { + // + // The host is telling us we're being resumed. At this point, code will + // already be running in the process, so a past timer may still expire and + // cause the code in Tick to run. Two interleavings are possible now: + // + // 1) We enter the gate first, and will adjust the cumulative inactive + // time delta used for correction. The code in Tick will have the + // illusion nothing happened and find itself RUNNING when entering + // the gate, resuming activities as before. + // + // 2) The code in Tick enters the gate first, and takes notice of the + // currently SUSPENDED state. It leaves the gate, entering the wait + // state for _resumeEvent. Next, we enter to adjust the cumulative + // inactive time delta, switch to the RUNNING state and signal the + // event for Tick to carry on and recompute its next due time based + // on the new cumulative delta. + // + lock (_gate) + { + if (_runState == Suspended) + { + _inactiveTime += _stopwatch!.Elapsed - _suspendedAt; // NB: Non-null when >= Running. + _runState = Running; + + if (!Environment.HasShutdownStarted) + { + _resumeEvent.Set(); + } + } + } + } + + private void RegisterHostLifecycleEventHandlers() + { + HostLifecycleService.Suspending += Suspending; + HostLifecycleService.Resuming += Resuming; + HostLifecycleService.AddRef(); + } + + private void UnregisterHostLifecycleEventHandlers() + { + HostLifecycleService.Suspending -= Suspending; + HostLifecycleService.Resuming -= Resuming; + HostLifecycleService.Release(); + } + } + + private sealed class SchedulePeriodicRecursive + { + private readonly IScheduler _scheduler; + private readonly TimeSpan _period; + private readonly Func _action; + + public SchedulePeriodicRecursive(IScheduler scheduler, TState state, TimeSpan period, Func action) + { + _scheduler = scheduler; + _period = period; + _action = action; + + _state = state; + } + + private TState _state; + private int _pendingTickCount; + private IDisposable? _cancel; + + public IDisposable Start() + { + _pendingTickCount = 0; + + var d = new SingleAssignmentDisposable(); + _cancel = d; + + d.Disposable = _scheduler.Schedule(TICK, _period, Tick); + + return d; + } + + // + // The protocol using the three commands is explained in the Tick implementation below. + // + private const int TICK = 0; + private const int DispatchStart = 1; + private const int DispatchEnd = 2; + + private void Tick(int command, Action recurse) + { + switch (command) + { + case TICK: + // + // Ticks keep going at the specified periodic rate. We do a head call such + // that no slippage is introduced because of DISPATCH_START work involving + // user code that may take arbitrarily long. + // + recurse(TICK, _period); + + // + // If we're not transitioning from 0 to 1 pending tick, another processing + // request is in flight which will see a non-zero pending tick count after + // doing the final decrement, causing it to reschedule immediately. We can + // safely bail out, delegating work to the catch-up tail calls. + // + if (Interlocked.Increment(ref _pendingTickCount) == 1) + { + goto case DispatchStart; + } + + break; + + case DispatchStart: + try + { + _state = _action(_state); + } + catch (Exception e) + { + _cancel!.Dispose(); // NB: Non-null after Start is called. + e.Throw(); + } + + // + // This is very subtle. We can't do a goto case DISPATCH_END here because it + // wouldn't introduce interleaving of periodic ticks that are due. In order + // to have best effort behavior for schedulers that don't have concurrency, + // we yield by doing a recursive call here. Notice this doesn't heal all of + // the problem, because the TICK commands that may be dispatched before the + // scheduled DISPATCH_END will do a "recurse(TICK, period)", which is relative + // from the point of entrance. Really all we're doing here is damage control + // for the case there's no stopwatch provider which should be rare (notice + // the LocalScheduler base class always imposes a stopwatch, but it can get + // disabled using DisableOptimizations; legacy implementations of schedulers + // from the v1.x days will not have a stopwatch). + // + recurse(DispatchEnd, TimeSpan.Zero); + + break; + + case DispatchEnd: + // + // If work was due while we were still running user code, the count will have + // been incremented by the periodic tick handler above. In that case, we will + // reschedule ourselves for dispatching work immediately. + // + // Notice we don't run a loop here, in order to allow interleaving of work on + // the scheduler by making recursive calls. In case we would use AsyncLock to + // ensure serialized execution the owner could get stuck in such a loop, thus + // we make tail calls to play nice with the scheduler. + // + if (Interlocked.Decrement(ref _pendingTickCount) > 0) + { + recurse(DispatchStart, TimeSpan.Zero); + } + + break; + } + } + } + + private sealed class EmulatedStopwatch : IStopwatch + { + private readonly IScheduler _scheduler; + private readonly DateTimeOffset _start; + + public EmulatedStopwatch(IScheduler scheduler) + { + _scheduler = scheduler; + _start = _scheduler.Now; + } + + public TimeSpan Elapsed => Normalize(_scheduler.Now - _start); + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/Scheduler.Services.cs b/LibExternal/System.Reactive/Concurrency/Scheduler.Services.cs new file mode 100644 index 0000000..f1bd239 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/Scheduler.Services.cs @@ -0,0 +1,79 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Concurrency +{ + // + // NOTE: When adding interface-based optimizations here, ensure to add the type to the list of + // interface-based optimizations used by DisableOptimizations and the RawScheduler type. + // + public static partial class Scheduler + { + internal static Type[] Optimizations = { + typeof(ISchedulerLongRunning), + typeof(IStopwatchProvider), + typeof(ISchedulerPeriodic) + /* update this list if new interface-based optimizations are added */ + }; + + /// + /// Returns the implementation of the specified scheduler, or null if no such implementation is available. + /// + /// Scheduler to get the implementation for. + /// The scheduler's implementation if available; null otherwise. + /// + /// This helper method is made available for query operator authors in order to discover scheduler services by using the required + /// IServiceProvider pattern, which allows for interception or redefinition of scheduler services. + /// + public static ISchedulerLongRunning? AsLongRunning(this IScheduler scheduler) => As(scheduler); + + /// + /// Returns the implementation of the specified scheduler, or null if no such implementation is available. + /// + /// Scheduler to get the implementation for. + /// The scheduler's implementation if available; null otherwise. + /// + /// + /// This helper method is made available for query operator authors in order to discover scheduler services by using the required + /// IServiceProvider pattern, which allows for interception or redefinition of scheduler services. + /// + /// + /// Consider using in case a stopwatch is required, but use of emulation stopwatch based + /// on the scheduler's clock is acceptable. Use of this method is recommended for best-effort use of the stopwatch provider + /// scheduler service, where the caller falls back to not using stopwatches if this facility wasn't found. + /// + /// + public static IStopwatchProvider? AsStopwatchProvider(this IScheduler scheduler) => As(scheduler); + + /// + /// Returns the implementation of the specified scheduler, or null if no such implementation is available. + /// + /// Scheduler to get the implementation for. + /// The scheduler's implementation if available; null otherwise. + /// + /// + /// This helper method is made available for query operator authors in order to discover scheduler services by using the required + /// IServiceProvider pattern, which allows for interception or redefinition of scheduler services. + /// + /// + /// Consider using the extension methods for in case periodic scheduling + /// is required and emulation of periodic behavior using other scheduler services is desirable. Use of this method is recommended + /// for best-effort use of the periodic scheduling service, where the caller falls back to not using periodic scheduling if this + /// facility wasn't found. + /// + /// + public static ISchedulerPeriodic? AsPeriodic(this IScheduler scheduler) => As(scheduler); + + private static T? As(IScheduler scheduler) + where T : class + { + if (scheduler is IServiceProvider svc) + { + return (T?)svc.GetService(typeof(T)); + } + + return null; + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/Scheduler.Simple.cs b/LibExternal/System.Reactive/Concurrency/Scheduler.Simple.cs new file mode 100644 index 0000000..1832139 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/Scheduler.Simple.cs @@ -0,0 +1,268 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; + +namespace System.Reactive.Concurrency +{ + public static partial class Scheduler + { + /// + /// Schedules an action to be executed. + /// + /// Scheduler to execute the action on. + /// Action to execute. + /// The disposable object used to cancel the scheduled action (best effort). + /// or is null. + public static IDisposable Schedule(this IScheduler scheduler, Action action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + // Surprisingly, passing the method group of Invoke will create a fresh + // delegate each time although it's static, while an anonymous + // lambda without the need of a closure will be cached. + // Once Roslyn supports caching delegates for method groups, + // the anonymous lambda can be replaced by the method group again. Until then, + // to avoid the repetition of code, the call to Invoke is left intact. + // Watch https://github.com/dotnet/roslyn/issues/5835 + return scheduler.Schedule(action, static (_, a) => Invoke(a)); + } + + /// + /// Schedules an action to be executed. + /// + /// Scheduler to execute the action on. + /// A state object to be passed to . + /// Action to execute. + /// The disposable object used to cancel the scheduled action (best effort). + /// or is null. + // Note: The naming of that method differs because otherwise, the signature would cause ambiguities. + internal static IDisposable ScheduleAction(this IScheduler scheduler, TState state, Action action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return scheduler.Schedule( + (action, state), + (_, tuple) => + { + tuple.action(tuple.state); + return Disposable.Empty; + }); + } + + /// + /// Schedules an action to be executed. + /// + /// Scheduler to execute the action on. + /// A state object to be passed to . + /// Action to execute. + /// The disposable object used to cancel the scheduled action (best effort). + /// or is null. + // Note: The naming of that method differs because otherwise, the signature would cause ambiguities. + internal static IDisposable ScheduleAction(this IScheduler scheduler, TState state, Func action) + { + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + + return scheduler.Schedule( + (action, state), + static (_, tuple) => tuple.action(tuple.state)); + } + + /// + /// Schedules an action to be executed after the specified relative due time. + /// + /// Scheduler to execute the action on. + /// Action to execute. + /// Relative time after which to execute the action. + /// The disposable object used to cancel the scheduled action (best effort). + /// or is null. + public static IDisposable Schedule(this IScheduler scheduler, TimeSpan dueTime, Action action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + // See note above. + return scheduler.Schedule(action, dueTime, static (_, a) => Invoke(a)); + } + + /// + /// Schedules an action to be executed after the specified relative due time. + /// + /// Scheduler to execute the action on. + /// Action to execute. + /// A state object to be passed to . + /// Relative time after which to execute the action. + /// The disposable object used to cancel the scheduled action (best effort). + /// or is null. + internal static IDisposable ScheduleAction(this IScheduler scheduler, TState state, TimeSpan dueTime, Action action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + // See note above. + return scheduler.Schedule((state, action), dueTime, static (_, tuple) => Invoke(tuple)); + } + + /// + /// Schedules an action to be executed after the specified relative due time. + /// + /// Scheduler to execute the action on. + /// Action to execute. + /// A state object to be passed to . + /// Relative time after which to execute the action. + /// The disposable object used to cancel the scheduled action (best effort). + /// or is null. + internal static IDisposable ScheduleAction(this IScheduler scheduler, TState state, TimeSpan dueTime, Func action) + { + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + + // See note above. + return scheduler.Schedule((state, action), dueTime, static (_, tuple) => Invoke(tuple)); + } + + /// + /// Schedules an action to be executed at the specified absolute due time. + /// + /// Scheduler to execute the action on. + /// Action to execute. + /// Absolute time at which to execute the action. + /// The disposable object used to cancel the scheduled action (best effort). + /// or is null. + public static IDisposable Schedule(this IScheduler scheduler, DateTimeOffset dueTime, Action action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + // See note above. + return scheduler.Schedule(action, dueTime, static (_, a) => Invoke(a)); + } + + /// + /// Schedules an action to be executed after the specified relative due time. + /// + /// Scheduler to execute the action on. + /// Action to execute. + /// A state object to be passed to . + /// Relative time after which to execute the action. + /// The disposable object used to cancel the scheduled action (best effort). + /// or is null. + internal static IDisposable ScheduleAction(this IScheduler scheduler, TState state, DateTimeOffset dueTime, Action action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + // See note above. + return scheduler.Schedule((state, action), dueTime, static (_, tuple) => Invoke(tuple)); + } + + /// + /// Schedules an action to be executed after the specified relative due time. + /// + /// Scheduler to execute the action on. + /// Action to execute. + /// A state object to be passed to . + /// Relative time after which to execute the action. + /// The disposable object used to cancel the scheduled action (best effort). + /// or is null. + internal static IDisposable ScheduleAction(this IScheduler scheduler, TState state, DateTimeOffset dueTime, Func action) + { + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + + // See note above. + return scheduler.Schedule((state, action), dueTime, static (_, tuple) => Invoke(tuple)); + } + + /// + /// Schedules an action to be executed. + /// + /// Scheduler to execute the action on. + /// Action to execute. + /// The disposable object used to cancel the scheduled action (best effort). + /// or is null. + public static IDisposable ScheduleLongRunning(this ISchedulerLongRunning scheduler, Action action) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return scheduler.ScheduleLongRunning(action, static (a, c) => a(c)); + } + + private static IDisposable Invoke(Action action) + { + action(); + return Disposable.Empty; + } + + private static IDisposable Invoke((TState state, Action action) tuple) + { + tuple.action(tuple.state); + return Disposable.Empty; + } + + private static IDisposable Invoke((TState state, Func action) tuple) + { + return tuple.action(tuple.state); + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/Scheduler.Wrappers.cs b/LibExternal/System.Reactive/Concurrency/Scheduler.Wrappers.cs new file mode 100644 index 0000000..0d6b86a --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/Scheduler.Wrappers.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Concurrency +{ + public static partial class Scheduler + { + /// + /// Returns a scheduler that represents the original scheduler, without any of its interface-based optimizations (e.g. long running scheduling). + /// + /// Scheduler to disable all optimizations for. + /// Proxy to the original scheduler but without any optimizations enabled. + /// is null. + public static IScheduler DisableOptimizations(this IScheduler scheduler) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return new DisableOptimizationsScheduler(scheduler); + } + + /// + /// Returns a scheduler that represents the original scheduler, without the specified set of interface-based optimizations (e.g. long running scheduling). + /// + /// Scheduler to disable the specified optimizations for. + /// Types of the optimization interfaces that have to be disabled. + /// Proxy to the original scheduler but without the specified optimizations enabled. + /// or is null. + public static IScheduler DisableOptimizations(this IScheduler scheduler, params Type[] optimizationInterfaces) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (optimizationInterfaces == null) + { + throw new ArgumentNullException(nameof(optimizationInterfaces)); + } + + return new DisableOptimizationsScheduler(scheduler, optimizationInterfaces); + } + + /// + /// Returns a scheduler that wraps the original scheduler, adding exception handling for scheduled actions. + /// + /// Type of the exception to check for. + /// Scheduler to apply an exception filter for. + /// Handler that's run if an exception is caught. The exception will be rethrown if the handler returns false. + /// Wrapper around the original scheduler, enforcing exception handling. + /// or is null. + public static IScheduler Catch(this IScheduler scheduler, Func handler) + where TException : Exception + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (handler == null) + { + throw new ArgumentNullException(nameof(handler)); + } + + return new CatchScheduler(scheduler, handler); + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/Scheduler.cs b/LibExternal/System.Reactive/Concurrency/Scheduler.cs new file mode 100644 index 0000000..d101b3e --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/Scheduler.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Globalization; +using System.Reactive.PlatformServices; + +namespace System.Reactive.Concurrency +{ + /// + /// Provides a set of static properties to access commonly used schedulers. + /// + public static partial class Scheduler + { + // TODO - Review whether this is too eager. + // Make first use of Scheduler trigger access to and initialization of the CAL. + + // HACK: Causes race condition with Locks in DefaultScheduler's static ctor chain + // private static DefaultScheduler s_default = DefaultScheduler.Instance; + + /// + /// Gets the current time according to the local machine's system clock. + /// + public static DateTimeOffset Now => SystemClock.UtcNow; + + /// + /// Normalizes the specified value to a positive value. + /// + /// The value to normalize. + /// The specified TimeSpan value if it is zero or positive; otherwise, . + public static TimeSpan Normalize(TimeSpan timeSpan) => timeSpan.Ticks < 0 ? TimeSpan.Zero : timeSpan; + + /// + /// Gets a scheduler that schedules work immediately on the current thread. + /// + public static ImmediateScheduler Immediate => ImmediateScheduler.Instance; + + /// + /// Gets a scheduler that schedules work as soon as possible on the current thread. + /// + public static CurrentThreadScheduler CurrentThread => CurrentThreadScheduler.Instance; + + /// + /// Gets a scheduler that schedules work on the platform's default scheduler. + /// + public static DefaultScheduler Default => DefaultScheduler.Instance; + + + // + // Notice we include all of the scheduler properties below, unconditionally. In Rx v2.0 + // beta and RC, we limited this a la carte menu to reflect the platform's capabilities. + // However, this caused different builds for Windows 8, .NET 4.5, and Portable Library + // to be required. In the RTM timeframe, we opted for unifying all of this based on a + // single Portable Library build of the core set of assemblies. As such, we're presented + // with a choice of either locking down those properties to the intersection, or keeping + // compatibility for those who upgrade from.NET 4.0 to .NET 4.5. We chose the latter, so + // we need to keep properties like NewThread here, even though they'll be obsolete from + // day 0 of Rx v2.0 (including our Portable Library story). Also, the NewThread one will + // be non-functional for Windows 8, causing a runtime exception to be thrown. + // + + + private static readonly Lazy _threadPool = new(static () => Initialize("ThreadPool")); + + /// + /// Gets a scheduler that schedules work on the thread pool. + /// + [Obsolete(Constants_Core.ObsoleteSchedulerThreadpool)] + public static IScheduler ThreadPool => _threadPool.Value; + + private static readonly Lazy _newThread = new(static () => Initialize("NewThread")); + + /// + /// Gets a scheduler that schedules work on a new thread using default thread creation options. + /// + [Obsolete(Constants_Core.ObsoleteSchedulerNewthread)] + public static IScheduler NewThread => _newThread.Value; + + private static readonly Lazy _taskPool = new(static () => Initialize("TaskPool")); + + /// + /// Gets a scheduler that schedules work on Task Parallel Library (TPL) task pool using the default TaskScheduler. + /// + [Obsolete(Constants_Core.ObsoleteSchedulerTaskpool)] + public static IScheduler TaskPool => _taskPool.Value; + + private static IScheduler Initialize(string name) + { + return PlatformEnlightenmentProvider.Current.GetService(name) + ?? throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Strings_Core.CANT_OBTAIN_SCHEDULER, name)); + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/SchedulerDefaults.cs b/LibExternal/System.Reactive/Concurrency/SchedulerDefaults.cs new file mode 100644 index 0000000..a24b28f --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/SchedulerDefaults.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Concurrency +{ + internal static class SchedulerDefaults + { + internal static IScheduler ConstantTimeOperations => ImmediateScheduler.Instance; + internal static IScheduler TailRecursion => ImmediateScheduler.Instance; + internal static IScheduler Iteration => CurrentThreadScheduler.Instance; + internal static IScheduler TimeBasedOperations => DefaultScheduler.Instance; + internal static IScheduler AsyncConversions => DefaultScheduler.Instance; + } +} diff --git a/LibExternal/System.Reactive/Concurrency/SchedulerOperation.cs b/LibExternal/System.Reactive/Concurrency/SchedulerOperation.cs new file mode 100644 index 0000000..77e86c2 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/SchedulerOperation.cs @@ -0,0 +1,151 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace System.Reactive.Concurrency +{ + /// + /// Represents an awaitable scheduler operation. Awaiting the object causes the continuation to be posted back to the originating scheduler's work queue. + /// + public sealed class SchedulerOperation + { + private readonly Func _schedule; + private readonly CancellationToken _cancellationToken; + private readonly bool _postBackToOriginalContext; + + internal SchedulerOperation(Func schedule, CancellationToken cancellationToken) + : this(schedule, false, cancellationToken) + { + } + + internal SchedulerOperation(Func schedule, bool postBackToOriginalContext, CancellationToken cancellationToken) + { + _schedule = schedule; + _cancellationToken = cancellationToken; + _postBackToOriginalContext = postBackToOriginalContext; + } + + /// + /// Controls whether the continuation is run on the originating synchronization context (false by default). + /// + /// true to run the continuation on the captured synchronization context; false otherwise (default). + /// Scheduler operation object with configured await behavior. + public SchedulerOperation ConfigureAwait(bool continueOnCapturedContext) + { + return new SchedulerOperation(_schedule, continueOnCapturedContext, _cancellationToken); + } + + /// + /// Gets an awaiter for the scheduler operation, used to post back the continuation. + /// + /// Awaiter for the scheduler operation. + public SchedulerOperationAwaiter GetAwaiter() + { + return new SchedulerOperationAwaiter(_schedule, _postBackToOriginalContext, _cancellationToken); + } + } + + /// + /// (Infrastructure) Scheduler operation awaiter type used by the code generated for C# await and Visual Basic Await expressions. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class SchedulerOperationAwaiter : INotifyCompletion + { + private readonly Func _schedule; + private readonly CancellationToken _cancellationToken; + private readonly bool _postBackToOriginalContext; + private readonly CancellationTokenRegistration _ctr; + + internal SchedulerOperationAwaiter(Func schedule, bool postBackToOriginalContext, CancellationToken cancellationToken) + { + _schedule = schedule; + _cancellationToken = cancellationToken; + _postBackToOriginalContext = postBackToOriginalContext; + + if (cancellationToken.CanBeCanceled) + { + _ctr = _cancellationToken.Register(static @this => ((SchedulerOperationAwaiter)@this!).Cancel(), this); + } + } + + /// + /// Indicates whether the scheduler operation has completed. Returns false unless cancellation was already requested. + /// + public bool IsCompleted => _cancellationToken.IsCancellationRequested; + + /// + /// Completes the scheduler operation, throwing an OperationCanceledException in case cancellation was requested. + /// + public void GetResult() => _cancellationToken.ThrowIfCancellationRequested(); + + /// + /// Registers the continuation with the scheduler operation. + /// + /// Continuation to be run on the originating scheduler. + public void OnCompleted(Action continuation) + { + if (continuation == null) + { + throw new ArgumentNullException(nameof(continuation)); + } + + if (_continuation != null) + { + throw new InvalidOperationException(Strings_Core.SCHEDULER_OPERATION_ALREADY_AWAITED); + } + + if (_postBackToOriginalContext) + { + var ctx = SynchronizationContext.Current; + if (ctx != null) + { + var original = continuation; + continuation = () => + { + // + // No need for OperationStarted and OperationCompleted calls here; + // this code is invoked through await support and will have a way + // to observe its start/complete behavior, either through returned + // Task objects or the async method builder's interaction with the + // SynchronizationContext object. + // + // In general though, Rx doesn't play nicely with synchronization + // contexts objects at the scheduler level. It's possible to start + // async operations by calling Schedule, without a way to observe + // their completion. Not interacting with SynchronizationContext + // is a conscious design decision as the performance impact was non + // negligible and our schedulers abstract over more constructs. + // + ctx.Post(static a => ((Action)a!)(), original); + }; + } + } + + var ran = 0; + + _continuation = () => + { + if (Interlocked.Exchange(ref ran, 1) == 0) + { + _ctr.Dispose(); // no null-check needed (struct) + continuation(); + } + }; + + _work = _schedule(_continuation); + } + + private volatile Action? _continuation; + private volatile IDisposable? _work; + + private void Cancel() + { + _work?.Dispose(); + _continuation?.Invoke(); + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/SchedulerQueue.cs b/LibExternal/System.Reactive/Concurrency/SchedulerQueue.cs new file mode 100644 index 0000000..a58fdc5 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/SchedulerQueue.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Concurrency +{ + /// + /// Efficient scheduler queue that maintains scheduled items sorted by absolute time. + /// + /// Absolute time representation type. + /// This type is not thread safe; users should ensure proper synchronization. + [Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix", Justification = "But it *is* a queue!")] + public class SchedulerQueue + where TAbsolute : IComparable + { + private readonly PriorityQueue> _queue; + + /// + /// Creates a new scheduler queue with a default initial capacity. + /// + public SchedulerQueue() + : this(1024) + { + } + + /// + /// Creates a new scheduler queue with the specified initial capacity. + /// + /// Initial capacity of the scheduler queue. + /// is less than zero. + public SchedulerQueue(int capacity) + { + if (capacity < 0) + { + throw new ArgumentOutOfRangeException(nameof(capacity)); + } + + _queue = new PriorityQueue>(capacity); + } + + /// + /// Gets the number of scheduled items in the scheduler queue. + /// + public int Count => _queue.Count; + + /// + /// Enqueues the specified work item to be scheduled. + /// + /// Work item to be scheduled. + public void Enqueue(ScheduledItem scheduledItem) => _queue.Enqueue(scheduledItem); + + /// + /// Removes the specified work item from the scheduler queue. + /// + /// Work item to be removed from the scheduler queue. + /// true if the item was found; false otherwise. + public bool Remove(ScheduledItem scheduledItem) => _queue.Remove(scheduledItem); + + /// + /// Dequeues the next work item from the scheduler queue. + /// + /// Next work item in the scheduler queue (removed). + public ScheduledItem Dequeue() => _queue.Dequeue(); + + /// + /// Peeks the next work item in the scheduler queue. + /// + /// Next work item in the scheduler queue (not removed). + public ScheduledItem Peek() => _queue.Peek(); + } +} diff --git a/LibExternal/System.Reactive/Concurrency/SchedulerWrapper.cs b/LibExternal/System.Reactive/Concurrency/SchedulerWrapper.cs new file mode 100644 index 0000000..5ab8535 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/SchedulerWrapper.cs @@ -0,0 +1,87 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Runtime.CompilerServices; + +namespace System.Reactive.Concurrency +{ + internal abstract class SchedulerWrapper : IScheduler, IServiceProvider + { + protected readonly IScheduler _scheduler; + private readonly ConditionalWeakTable _cache; + + protected SchedulerWrapper(IScheduler scheduler) + { + _scheduler = scheduler; + _cache = new ConditionalWeakTable(); + } + + protected SchedulerWrapper(IScheduler scheduler, ConditionalWeakTable cache) + { + _scheduler = scheduler; + _cache = cache; + } + + public DateTimeOffset Now => _scheduler.Now; + + public IDisposable Schedule(TState state, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return _scheduler.Schedule(state, Wrap(action)); + } + + public IDisposable Schedule(TState state, TimeSpan dueTime, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return _scheduler.Schedule(state, dueTime, Wrap(action)); + } + + public IDisposable Schedule(TState state, DateTimeOffset dueTime, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return _scheduler.Schedule(state, dueTime, Wrap(action)); + } + + protected virtual Func Wrap(Func action) + { + return (self, state) => action(GetRecursiveWrapper(self), state); + } + + protected IScheduler GetRecursiveWrapper(IScheduler scheduler) + { + return _cache.GetValue(scheduler, s => Clone(s, _cache)); + } + + protected abstract SchedulerWrapper Clone(IScheduler scheduler, ConditionalWeakTable cache); + + public object? GetService(Type serviceType) + { + if (_scheduler is not IServiceProvider serviceProvider) + { + return null; + } + + if (TryGetService(serviceProvider, serviceType, out var result)) + { + return result; + } + + return serviceProvider.GetService(serviceType); + } + + protected abstract bool TryGetService(IServiceProvider provider, Type serviceType, out object? service); + } +} diff --git a/LibExternal/System.Reactive/Concurrency/Synchronization.ObserveOn.cs b/LibExternal/System.Reactive/Concurrency/Synchronization.ObserveOn.cs new file mode 100644 index 0000000..864acf9 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/Synchronization.ObserveOn.cs @@ -0,0 +1,129 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Threading; + +namespace System.Reactive.Concurrency +{ + internal static class ObserveOn + { + /// + /// The new ObserveOn operator run with an IScheduler in a lock-free manner. + /// + internal sealed class Scheduler : Producer> + { + private readonly IObservable _source; + private readonly IScheduler _scheduler; + + public Scheduler(IObservable source, IScheduler scheduler) + { + _source = source; + _scheduler = scheduler; + } + + protected override ObserveOnObserverNew CreateSink(IObserver observer) => new(_scheduler, observer); + + protected override void Run(ObserveOnObserverNew sink) => sink.Run(_source); + } + + /// + /// The new ObserveOn operator run with an ISchedulerLongRunning in a mostly lock-free manner. + /// + internal sealed class SchedulerLongRunning : Producer> + { + private readonly IObservable _source; + private readonly ISchedulerLongRunning _scheduler; + + public SchedulerLongRunning(IObservable source, ISchedulerLongRunning scheduler) + { + _source = source; + _scheduler = scheduler; + } + + protected override ObserveOnObserverLongRunning CreateSink(IObserver observer) => new(_scheduler, observer); + + protected override void Run(ObserveOnObserverLongRunning sink) => sink.Run(_source); + } + + internal sealed class Context : Producer + { + private readonly IObservable _source; + private readonly SynchronizationContext _context; + + public Context(IObservable source, SynchronizationContext context) + { + _source = source; + _context = context; + } + + protected override _ CreateSink(IObserver observer) => new(_context, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly SynchronizationContext _context; + + public _(SynchronizationContext context, IObserver observer) + : base(observer) + { + _context = context; + } + + public override void Run(IObservable source) + { + // + // The interactions with OperationStarted/OperationCompleted below allow + // for test frameworks to wait until a whole sequence is observed, running + // asserts on a per-message level. Also, for ASP.NET pages, the use of the + // built-in synchronization context would allow processing to finished in + // its entirety before moving on with the page lifecycle. + // + _context.OperationStarted(); + + SetUpstream(source.SubscribeSafe(this)); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _context.OperationCompleted(); + } + base.Dispose(disposing); + } + + public override void OnNext(TSource value) + { + _context.Post(OnNextPosted, value); + } + + public override void OnError(Exception error) + { + _context.Post(OnErrorPosted, error); + } + + public override void OnCompleted() + { + _context.Post(OnCompletedPosted, state: null); + } + + private void OnNextPosted(object? value) + { + ForwardOnNext((TSource)value!); + } + + private void OnErrorPosted(object? error) + { + ForwardOnError((Exception)error!); + } + + private void OnCompletedPosted(object? ignored) + { + ForwardOnCompleted(); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/Synchronization.Synchronize.cs b/LibExternal/System.Reactive/Concurrency/Synchronization.Synchronize.cs new file mode 100644 index 0000000..b8579e7 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/Synchronization.Synchronize.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Concurrency +{ + internal sealed class Synchronize : Producer._> + { + private readonly IObservable _source; + private readonly object? _gate; + + public Synchronize(IObservable source, object gate) + { + _source = source; + _gate = gate; + } + + public Synchronize(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly object _gate; + + public _(Synchronize parent, IObserver observer) + : base(observer) + { + _gate = parent._gate ?? new object(); + } + + public override void OnNext(TSource value) + { + lock (_gate) + { + ForwardOnNext(value); + } + } + + public override void OnError(Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + lock (_gate) + { + ForwardOnCompleted(); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/Synchronization.cs b/LibExternal/System.Reactive/Concurrency/Synchronization.cs new file mode 100644 index 0000000..b4a5b53 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/Synchronization.cs @@ -0,0 +1,260 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Reactive.Disposables; +using System.Threading; + +namespace System.Reactive.Concurrency +{ + /// + /// Provides basic synchronization and scheduling services for observable sequences. + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static class Synchronization + { + #region SubscribeOn + + /// + /// Wraps the source sequence in order to run its subscription and unsubscription logic on the specified scheduler. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Scheduler to perform subscription and unsubscription actions on. + /// The source sequence whose subscriptions and unsubscriptions happen on the specified scheduler. + /// or is null. + /// + /// Only the side-effects of subscribing to the source sequence and disposing subscriptions to the source sequence are run on the specified scheduler. + /// In order to invoke observer callbacks on the specified scheduler, e.g. to offload callback processing to a dedicated thread, use . + /// + public static IObservable SubscribeOn(IObservable source, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return new SubscribeOnObservable(source, scheduler); + } + + private sealed class SubscribeOnObservable : ObservableBase + { + private sealed class Subscription : IDisposable + { + private SerialDisposableValue _cancel; + + public Subscription(IObservable source, IScheduler scheduler, IObserver observer) + { + _cancel.TrySetFirst( + scheduler.Schedule( + (@this: this, source, observer), + (closureScheduler, state) => + { + state.@this._cancel.Disposable = new ScheduledDisposable(closureScheduler, state.source.SubscribeSafe(state.observer)); + return Disposable.Empty; + })); + } + + public void Dispose() + { + _cancel.Dispose(); + } + } + + private readonly IObservable _source; + private readonly IScheduler _scheduler; + + public SubscribeOnObservable(IObservable source, IScheduler scheduler) + { + _source = source; + _scheduler = scheduler; + } + + protected override IDisposable SubscribeCore(IObserver observer) + { + return new Subscription(_source, _scheduler, observer); + } + } + + /// + /// Wraps the source sequence in order to run its subscription and unsubscription logic on the specified synchronization context. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Synchronization context to perform subscription and unsubscription actions on. + /// The source sequence whose subscriptions and unsubscriptions happen on the specified synchronization context. + /// or is null. + /// + /// Only the side-effects of subscribing to the source sequence and disposing subscriptions to the source sequence are run on the specified synchronization context. + /// In order to invoke observer callbacks on the specified synchronization context, e.g. to post callbacks to a UI thread represented by the synchronization context, use . + /// + public static IObservable SubscribeOn(IObservable source, SynchronizationContext context) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + return new SubscribeOnCtxObservable(source, context); + } + + private sealed class SubscribeOnCtxObservable : ObservableBase + { + private sealed class Subscription : IDisposable + { + private readonly IObservable _source; + private readonly IObserver _observer; + private readonly SynchronizationContext _context; + private SingleAssignmentDisposableValue _cancel; + + public Subscription(IObservable source, SynchronizationContext context, IObserver observer) + { + _source = source; + _context = context; + _observer = observer; + + context.PostWithStartComplete( + @this => + { + if (!@this._cancel.IsDisposed) + { + @this._cancel.Disposable = new ContextDisposable(@this._context, @this._source.SubscribeSafe(@this._observer)); + } + }, + this); + } + + public void Dispose() + { + _cancel.Dispose(); + } + } + + private readonly IObservable _source; + private readonly SynchronizationContext _context; + + public SubscribeOnCtxObservable(IObservable source, SynchronizationContext context) + { + _source = source; + _context = context; + } + + protected override IDisposable SubscribeCore(IObserver observer) + { + return new Subscription(_source, _context, observer); + } + } + + #endregion + + #region ObserveOn + + /// + /// Wraps the source sequence in order to run its observer callbacks on the specified scheduler. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Scheduler to notify observers on. + /// The source sequence whose observations happen on the specified scheduler. + /// or is null. + public static IObservable ObserveOn(IObservable source, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + var longRunning = scheduler.AsLongRunning(); + if (longRunning != null) + { + return new ObserveOn.SchedulerLongRunning(source, longRunning); + } + return new ObserveOn.Scheduler(source, scheduler); + } + + /// + /// Wraps the source sequence in order to run its observer callbacks on the specified synchronization context. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Synchronization context to notify observers on. + /// The source sequence whose observations happen on the specified synchronization context. + /// or is null. + public static IObservable ObserveOn(IObservable source, SynchronizationContext context) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + return new ObserveOn.Context(source, context); + } + + #endregion + + #region Synchronize + + /// + /// Wraps the source sequence in order to ensure observer callbacks are properly serialized. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// The source sequence whose outgoing calls to observers are synchronized. + /// is null. + public static IObservable Synchronize(IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return new Synchronize(source); + } + + /// + /// Wraps the source sequence in order to ensure observer callbacks are synchronized using the specified gate object. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Gate object to synchronize each observer call on. + /// The source sequence whose outgoing calls to observers are synchronized on the given gate object. + /// or is null. + public static IObservable Synchronize(IObservable source, object gate) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (gate == null) + { + throw new ArgumentNullException(nameof(gate)); + } + + return new Synchronize(source, gate); + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Concurrency/SynchronizationContextScheduler.cs b/LibExternal/System.Reactive/Concurrency/SynchronizationContextScheduler.cs new file mode 100644 index 0000000..260576d --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/SynchronizationContextScheduler.cs @@ -0,0 +1,100 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; +using System.Threading; + +namespace System.Reactive.Concurrency +{ + /// + /// Represents an object that schedules units of work on a provided . + /// + public class SynchronizationContextScheduler : LocalScheduler + { + private readonly SynchronizationContext _context; + private readonly bool _alwaysPost; + + /// + /// Creates an object that schedules units of work on the provided . + /// + /// Synchronization context to schedule units of work on. + /// is null. + public SynchronizationContextScheduler(SynchronizationContext context) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + _alwaysPost = true; + } + + /// + /// Creates an object that schedules units of work on the provided . + /// + /// Synchronization context to schedule units of work on. + /// Configures whether scheduling always posts to the synchronization context, regardless whether the caller is on the same synchronization context. + /// is null. + public SynchronizationContextScheduler(SynchronizationContext context, bool alwaysPost) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + _alwaysPost = alwaysPost; + } + + /// + /// Schedules an action to be executed. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public override IDisposable Schedule(TState state, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (!_alwaysPost && _context == SynchronizationContext.Current) + { + return action(this, state); + } + + var d = new SingleAssignmentDisposable(); + + _context.PostWithStartComplete(() => + { + if (!d.IsDisposed) + { + d.Disposable = action(this, state); + } + }); + + return d; + } + + /// + /// Schedules an action to be executed after dueTime. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// Relative time after which to execute the action. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var dt = Scheduler.Normalize(dueTime); + if (dt.Ticks == 0) + { + return Schedule(state, action); + } + + // Note that avoiding closure allocation here would introduce infinite generic recursion over the TState argument + return DefaultScheduler.Instance.Schedule(state, dt, (_, state1) => Schedule(state1, action)); + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/TaskHelpers.cs b/LibExternal/System.Reactive/Concurrency/TaskHelpers.cs new file mode 100644 index 0000000..bb43156 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/TaskHelpers.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Reactive.Concurrency +{ + internal static class TaskHelpers + { + private const int MaxDelay = int.MaxValue; + + public static Task Delay(TimeSpan delay, CancellationToken token) + { + var milliseconds = (long)delay.TotalMilliseconds; + + if (milliseconds > MaxDelay) + { + var remainder = delay - TimeSpan.FromMilliseconds(MaxDelay); + + return + Task.Delay(MaxDelay, token) + .ContinueWith(_ => Delay(remainder, token), TaskContinuationOptions.ExecuteSynchronously) + .Unwrap(); + } + + return Task.Delay(delay, token); + } + + public static Exception GetSingleException(this Task t) + { + Debug.Assert(t.IsFaulted && t.Exception != null); + + if (t.Exception!.InnerException != null) + { + return t.Exception.InnerException; + } + + return t.Exception; + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/TaskObservationOptions.cs b/LibExternal/System.Reactive/Concurrency/TaskObservationOptions.cs new file mode 100644 index 0000000..74fa071 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/TaskObservationOptions.cs @@ -0,0 +1,132 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; + +namespace System.Reactive.Concurrency +{ + /// + /// Controls how completion or failure is handled when a or + /// is wrapped as an and observed by + /// an . + /// + /// + /// + /// This type can be passed to overloads of the various method that adapt a TPL task as an + /// . It deals with two concerns that arise whenever this is done: + /// the scheduler through which notifications are delivered, and the handling of exceptions + /// that occur after all observers have unsubscribed. + /// + /// + /// If the property is non-null, it will be used to deliver all + /// notifications to observers, whether those notifications occur immediately (because the task + /// had already finished by the time it was observed) or they happen later. + /// + /// + /// The property determines how to deal with tasks + /// that fail after unsubscription (i.e., if an application calls + /// on an observable wrapping, then calls Dispose on the result before that task completes, and + /// the task subsequently enters a faulted state). Overloads that don't take a + /// argument do not observe the in this case, with the result that + /// the exception will then emerge from + /// (which could terminate the process, depending on how the .NET application has been + /// configured). This is consistent with how unobserved failures are + /// normally handled, but it is not consistent with how Rx handles post-unsubcription failures + /// in general. For example, if the projection callback for Select is in progress at the moment + /// an observer unsubscribes, and that callback then goes on to throw an exception, that + /// exception is simply swallowed. (One could argue that it should instead be sent to some + /// application-level unhandled exception handler, but the current behaviour has been in place + /// for well over a decade, so it's not something we can change.) So there is an argument that + /// post-unsubscribe failures in -wrapped tasks should be + /// ignored in exactly the same way: the default behaviour for post-unsubscribe failures in + /// tasks is inconsistent with the handling of all other post-unsubscribe failures. This has + /// also been the case for over a decade, so that inconsistency of defaults cannot be changed, + /// but the property enables applications to + /// ask for task-originated post-unsubscribe exceptions to be ignored in the same way as + /// non-task-originated post-unsubscribe exceptions are. (Where possible, applications should + /// avoid getting into situations where they throw exceptions in scenarios where nothing is + /// able to observe them is. This setting is a last resort for situations in which this is + /// truly unavoidable.) + /// + /// + public sealed class TaskObservationOptions + { + public TaskObservationOptions( + IScheduler? scheduler, + bool ignoreExceptionsAfterUnsubscribe) + { + Scheduler = scheduler; + IgnoreExceptionsAfterUnsubscribe = ignoreExceptionsAfterUnsubscribe; + } + + /// + /// Gets the optional scheduler to use when delivering notifications of the tasks's + /// progress. + /// + /// + /// If this is null, the behaviour depends on whether the task has already completed. If + /// the task has finished, the relevant completion or error notifications will be delivered + /// via . If the task is still running (or not yet + /// started) at the instant at which it is observed through Rx, no scheduler will be used + /// if this property is null. + /// + public IScheduler? Scheduler { get; } + + /// + /// Gets a flag controlling handling of exceptions that occur after cancellation + /// has been initiated by unsubscribing from the observable representing the task's + /// progress. + /// + /// + /// If this is true, exceptions that occur after all observers have unsubscribed + /// will be handled and silently ignored. If false, they will go unobserved, meaning + /// they will eventually emerge through . + /// + public bool IgnoreExceptionsAfterUnsubscribe { get; } + + internal Value ToValue() => new(Scheduler, IgnoreExceptionsAfterUnsubscribe); + + /// + /// Value-type representation. + /// + /// + /// + /// The public API surface area for is a class because + /// using a value type would run into various issues. The type might appear in expression + /// trees due to use of , which limits us + /// to a fairly old subset of C#. It means we can't use the in modifier on + /// parameters, which in turn prevents us from passing options by reference, increasing the + /// overhead of each method call. Also, options types such as this aren't normally value + /// types, so it would be a curious design choice. + /// + /// + /// The downside of using a class is that it entails an extra allocation. Since the feature + /// for which this is designed (the ability to swallow unhandled exceptions thrown by tasks + /// after unsubscription) is one we don't expect most applications to use, that shouldn't + /// be a problem. However, to accommodate this feature, common code paths shared by various + /// overloads need the information that a holds. The + /// easy approach would be to construct an instance of this type in overloads that don't + /// take one as an argument. But that would be impose an additional allocation on code that + /// doesn't want this new feature. + /// + /// + /// So although we can't use a value type with in in public APIs dues to constraints + /// on expression trees, we can do so internally. This type is a value-typed version of + /// enabling us to share code paths without forcing + /// new allocations on existing code. + /// + /// + internal readonly struct Value + { + internal Value(IScheduler? scheduler, bool ignoreExceptionsAfterUnsubscribe) + { + Scheduler = scheduler; + IgnoreExceptionsAfterUnsubscribe = ignoreExceptionsAfterUnsubscribe; + } + + public IScheduler? Scheduler { get; } + public bool IgnoreExceptionsAfterUnsubscribe { get; } + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/TaskPoolScheduler.cs b/LibExternal/System.Reactive/Concurrency/TaskPoolScheduler.cs new file mode 100644 index 0000000..ee3af96 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/TaskPoolScheduler.cs @@ -0,0 +1,315 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Reactive.Concurrency +{ + /// + /// Represents an object that schedules units of work on the Task Parallel Library (TPL) task pool. + /// + /// Instance of this type using the default TaskScheduler to schedule work on the TPL task pool. + public sealed class TaskPoolScheduler : LocalScheduler, ISchedulerLongRunning, ISchedulerPeriodic + { + private sealed class ScheduledWorkItem : IDisposable + { + private readonly TState _state; + private readonly TaskPoolScheduler _scheduler; + private readonly Func _action; + + private SerialDisposableValue _cancel; + + public ScheduledWorkItem(TaskPoolScheduler scheduler, TState state, Func action) + { + _state = state; + _action = action; + _scheduler = scheduler; + + var cancelable = new CancellationDisposable(); + + _cancel.Disposable = cancelable; + + scheduler._taskFactory.StartNew( + thisObject => + { + var @this = (ScheduledWorkItem)thisObject!; + // + // BREAKING CHANGE v2.0 > v1.x - No longer escalating exceptions using a throwing + // helper thread. + // + // Our manual escalation based on the creation of a throwing thread was merely to + // expedite the process of throwing the exception that would otherwise occur on the + // finalizer thread at a later point during the app's lifetime. + // + // However, it also prevented applications from observing the exception through + // the TaskScheduler.UnobservedTaskException static event. Also, starting form .NET + // 4.5, the default behavior of the task pool is not to take down the application + // when an exception goes unobserved (done as part of the async/await work). It'd + // be weird for Rx not to follow the platform defaults. + // + // General implementation guidelines for schedulers (in order of importance): + // + // 1. Always thunk through to the underlying infrastructure with a wrapper that's as tiny as possible. + // 2. Global exception notification/handling mechanisms shouldn't be bypassed. + // 3. Escalation behavior for exceptions is left to the underlying infrastructure. + // + // The Catch extension method for IScheduler (added earlier) allows to re-route + // exceptions at stage 2. If the exception isn't handled at the Rx level, it + // propagates by means of a rethrow, falling back to behavior in 3. + // + @this._cancel.Disposable = @this._action(@this._scheduler, @this._state); + }, + this, + cancelable.Token); + } + + public void Dispose() + { + _cancel.Dispose(); + } + } + + private sealed class SlowlyScheduledWorkItem : IDisposable + { + private readonly TState _state; + private readonly TaskPoolScheduler _scheduler; + private readonly Func _action; + + private MultipleAssignmentDisposableValue _cancel; + + public SlowlyScheduledWorkItem(TaskPoolScheduler scheduler, TState state, TimeSpan dueTime, Func action) + { + _state = state; + _action = action; + _scheduler = scheduler; + + var ct = new CancellationDisposable(); + _cancel.Disposable = ct; + + TaskHelpers.Delay(dueTime, ct.Token).ContinueWith( + (_, thisObject) => + { + var @this = (SlowlyScheduledWorkItem)thisObject!; + + if (!@this._cancel.IsDisposed) + { + @this._cancel.Disposable = @this._action(@this._scheduler, @this._state); + } + }, + this, + CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion, + scheduler._taskFactory.Scheduler ?? TaskScheduler.Default); + } + + public void Dispose() + { + _cancel.Dispose(); + } + } + + private sealed class LongScheduledWorkItem : ICancelable + { + private readonly TState _state; + private readonly Action _action; + + private SingleAssignmentDisposableValue _cancel; + + public LongScheduledWorkItem(TaskPoolScheduler scheduler, TState state, Action action) + { + _state = state; + _action = action; + + scheduler._taskFactory.StartNew( + thisObject => + { + var @this = (LongScheduledWorkItem)thisObject!; + + // + // Notice we don't check _cancel.IsDisposed. The contract for ISchedulerLongRunning + // requires us to ensure the scheduled work gets an opportunity to observe + // the cancellation request. + // + @this._action(@this._state, @this); + }, + this, + TaskCreationOptions.LongRunning); + } + + public void Dispose() + { + _cancel.Dispose(); + } + + public bool IsDisposed => _cancel.IsDisposed; + } + + private static readonly Lazy LazyInstance = new(static () => new TaskPoolScheduler(new TaskFactory(TaskScheduler.Default))); + private readonly TaskFactory _taskFactory; + + /// + /// Creates an object that schedules units of work using the provided . + /// + /// Task factory used to create tasks to run units of work. + /// is null. + public TaskPoolScheduler(TaskFactory taskFactory) + { + _taskFactory = taskFactory ?? throw new ArgumentNullException(nameof(taskFactory)); + } + + /// + /// Gets an instance of this scheduler that uses the default . + /// + public static TaskPoolScheduler Default => LazyInstance.Value; + + /// + /// Schedules an action to be executed. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public override IDisposable Schedule(TState state, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return new ScheduledWorkItem(this, state, action); + } + + /// + /// Schedules an action to be executed after dueTime. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// Relative time after which to execute the action. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var dt = Scheduler.Normalize(dueTime); + if (dt.Ticks == 0) + { + return Schedule(state, action); + } + + return ScheduleSlow(state, dt, action); + } + + private IDisposable ScheduleSlow(TState state, TimeSpan dueTime, Func action) + { + return new SlowlyScheduledWorkItem(this, state, dueTime, action); + } + + /// + /// Schedules a long-running task by creating a new task using TaskCreationOptions.LongRunning. Cancellation happens through polling. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public IDisposable ScheduleLongRunning(TState state, Action action) + { + return new LongScheduledWorkItem(this, state, action); + } + + /// + /// Gets a new stopwatch object. + /// + /// New stopwatch object; started at the time of the request. + public override IStopwatch StartStopwatch() + { + // + // Strictly speaking, this explicit override is not necessary because the base implementation calls into + // the enlightenment module to obtain the CAL, which would circle back to System.Reactive.PlatformServices + // where we're currently running. This is merely a short-circuit to avoid the additional roundtrip. + // + return new StopwatchImpl(); + } + + /// + /// Schedules a periodic piece of work by running a platform-specific timer to create tasks periodically. + /// + /// The type of the state passed to the scheduled action. + /// Initial state passed to the action upon the first iteration. + /// Period for running the work periodically. + /// Action to be executed, potentially updating the state. + /// The disposable object used to cancel the scheduled recurring action (best effort). + /// is null. + /// is less than . + public IDisposable SchedulePeriodic(TState state, TimeSpan period, Func action) + { + if (period < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(period)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return new PeriodicallyScheduledWorkItem(state, period, action, _taskFactory); + } + + private sealed class PeriodicallyScheduledWorkItem : IDisposable + { + private TState _state; + + private readonly TimeSpan _period; + private readonly TaskFactory _taskFactory; + private readonly Func _action; + private readonly AsyncLock _gate = new(); + private readonly CancellationTokenSource _cts = new(); + + public PeriodicallyScheduledWorkItem(TState state, TimeSpan period, Func action, TaskFactory taskFactory) + { + _state = state; + _period = period; + _action = action; + _taskFactory = taskFactory; + + MoveNext(); + } + + public void Dispose() + { + _cts.Cancel(); + _gate.Dispose(); + } + + private void MoveNext() + { + TaskHelpers.Delay(_period, _cts.Token).ContinueWith( + static (_, thisObject) => + { + var @this = (PeriodicallyScheduledWorkItem)thisObject!; + + @this.MoveNext(); + + @this._gate.Wait( + @this, + static closureThis => closureThis._state = closureThis._action(closureThis._state)); + }, + this, + CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion, + _taskFactory.Scheduler ?? TaskScheduler.Default + ); + } + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/ThreadPoolScheduler.Windows.cs b/LibExternal/System.Reactive/Concurrency/ThreadPoolScheduler.Windows.cs new file mode 100644 index 0000000..c7bd55f --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/ThreadPoolScheduler.Windows.cs @@ -0,0 +1,192 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +#if LEGACY_WINRT +using System.ComponentModel; +using Windows.System.Threading; + +namespace System.Reactive.Concurrency +{ + /// + /// Represents an object that schedules units of work on the Windows Runtime thread pool. + /// + /// Singleton instance of this type exposed through this static property. + [CLSCompliant(false)] + public sealed class ThreadPoolScheduler : LocalScheduler, ISchedulerPeriodic + { + private static readonly Lazy LazyDefault = new(static () => new ThreadPoolScheduler()); + + /// + /// Constructs a ThreadPoolScheduler that schedules units of work on the Windows ThreadPool. + /// + public ThreadPoolScheduler() + { + } + + /// + /// Constructs a ThreadPoolScheduler that schedules units of work on the Windows ThreadPool with the given priority. + /// + /// Priority for scheduled units of work. + public ThreadPoolScheduler(WorkItemPriority priority) + { + Priority = priority; + Options = WorkItemOptions.None; + } + + /// + /// Constructs a ThreadPoolScheduler that schedules units of work on the Windows ThreadPool with the given priority. + /// + /// Priority for scheduled units of work. + /// Options that configure how work is scheduled. + public ThreadPoolScheduler(WorkItemPriority priority, WorkItemOptions options) + { + Priority = priority; + Options = options; + } + + /// + /// Gets the singleton instance of the Windows Runtime thread pool scheduler. + /// + [Obsolete("Use the Instance property", false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public static ThreadPoolScheduler Default => LazyDefault.Value; + + /// + /// Gets the singleton instance of the Windows Runtime thread pool scheduler. + /// + public static ThreadPoolScheduler Instance => LazyDefault.Value; + + /// + /// Gets the priority at which work is scheduled. + /// + public WorkItemPriority Priority { get; } + + /// + /// Gets the options that configure how work is scheduled. + /// + public WorkItemOptions Options { get; } + + /// + /// Schedules an action to be executed. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public override IDisposable Schedule(TState state, Func action) + { + if (action == null) + throw new ArgumentNullException(nameof(action)); + + var userWorkItem = new UserWorkItem(this, state, action); + + var res = ThreadPool.RunAsync( + iaa => userWorkItem.Run(), + Priority, + Options); + + userWorkItem.CancelQueueDisposable = res.AsDisposable(); + + return userWorkItem; + } + + /// + /// Schedules an action to be executed after dueTime, using a Windows.System.Threading.ThreadPoolTimer object. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// Relative time after which to execute the action. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action) + { + if (action == null) + throw new ArgumentNullException(nameof(action)); + + var dt = Scheduler.Normalize(dueTime); + + if (dt.Ticks == 0) + { + return Schedule(state, action); + } + + return ScheduleSlow(state, dt, action); + } + + private IDisposable ScheduleSlow(TState state, TimeSpan dueTime, Func action) + { + var userWorkItem = new UserWorkItem(this, state, action); + + var res = ThreadPoolTimer.CreateTimer( + tpt => userWorkItem.Run(), + dueTime); + + userWorkItem.CancelQueueDisposable = res.AsDisposable(); + + return userWorkItem; + } + + /// + /// Schedules a periodic piece of work, using a Windows.System.Threading.ThreadPoolTimer object. + /// + /// The type of the state passed to the scheduled action. + /// Initial state passed to the action upon the first iteration. + /// Period for running the work periodically. + /// Action to be executed, potentially updating the state. + /// The disposable object used to cancel the scheduled recurring action (best effort). + /// is null. + /// is less than one millisecond. + public IDisposable SchedulePeriodic(TState state, TimeSpan period, Func action) + { + // + // The WinRT thread pool is based on the Win32 thread pool and cannot handle + // sub-1ms resolution. When passing a lower period, we get single-shot + // timer behavior instead. See MSDN documentation for CreatePeriodicTimer + // for more information. + // + if (period < TimeSpan.FromMilliseconds(1)) + throw new ArgumentOutOfRangeException(nameof(period), Strings_PlatformServices.WINRT_NO_SUB1MS_TIMERS); + if (action == null) + throw new ArgumentNullException(nameof(action)); + + return new PeriodicallyScheduledWorkItem(state, period, action); + } + + private sealed class PeriodicallyScheduledWorkItem : IDisposable + { + private TState _state; + private Func _action; + + private readonly ThreadPoolTimer _timer; + private readonly AsyncLock _gate = new(); + + public PeriodicallyScheduledWorkItem(TState state, TimeSpan period, Func action) + { + _state = state; + _action = action; + + _timer = ThreadPoolTimer.CreatePeriodicTimer( + Tick, + period); + } + + private void Tick(ThreadPoolTimer timer) + { + _gate.Wait( + this, + static @this => @this._state = @this._action(@this._state)); + } + + public void Dispose() + { + _timer.Cancel(); + _gate.Dispose(); + _action = Stubs.I; + } + } + } +} +#endif diff --git a/LibExternal/System.Reactive/Concurrency/ThreadPoolScheduler.cs b/LibExternal/System.Reactive/Concurrency/ThreadPoolScheduler.cs new file mode 100644 index 0000000..d9163ec --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/ThreadPoolScheduler.cs @@ -0,0 +1,227 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +#if !LEGACY_WINRT +using System.Reactive.Disposables; +using System.Threading; + +namespace System.Reactive.Concurrency +{ + /// + /// Represents an object that schedules units of work on the CLR thread pool. + /// + /// Singleton instance of this type exposed through this static property. + public sealed class ThreadPoolScheduler : LocalScheduler, ISchedulerLongRunning, ISchedulerPeriodic + { + private static readonly Lazy LazyInstance = new(static () => new ThreadPoolScheduler()); + private static readonly Lazy LazyNewBackgroundThread = new(static () => new NewThreadScheduler(action => new Thread(action) { IsBackground = true })); + + /// + /// Gets the singleton instance of the CLR thread pool scheduler. + /// + public static ThreadPoolScheduler Instance => LazyInstance.Value; + + private ThreadPoolScheduler() + { + } + + /// + /// Schedules an action to be executed. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public override IDisposable Schedule(TState state, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var workItem = new UserWorkItem(this, state, action); + + ThreadPool.QueueUserWorkItem( + static closureWorkItem => ((UserWorkItem)closureWorkItem!).Run(), + workItem); + + return workItem; + } + + /// + /// Schedules an action to be executed after dueTime, using a System.Threading.Timer object. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// Relative time after which to execute the action. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var dt = Scheduler.Normalize(dueTime); + if (dt.Ticks == 0) + { + return Schedule(state, action); + } + + var workItem = new UserWorkItem(this, state, action); + + workItem.CancelQueueDisposable = new Timer( + static closureWorkItem => ((UserWorkItem)closureWorkItem!).Run(), + workItem, + dt, + Timeout.InfiniteTimeSpan); + + return workItem; + } + + /// + /// Schedules a long-running task by creating a new thread. Cancellation happens through polling. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public IDisposable ScheduleLongRunning(TState state, Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return LazyNewBackgroundThread.Value.ScheduleLongRunning(state, action); + } + + /// + /// Starts a new stopwatch object. + /// + /// New stopwatch object; started at the time of the request. + public override IStopwatch StartStopwatch() + { + // + // Strictly speaking, this explicit override is not necessary because the base implementation calls into + // the enlightenment module to obtain the CAL, which would circle back to System.Reactive.PlatformServices + // where we're currently running. This is merely a short-circuit to avoid the additional roundtrip. + // + return new StopwatchImpl(); + } + + /// + /// Schedules a periodic piece of work, using a System.Threading.Timer object. + /// + /// The type of the state passed to the scheduled action. + /// Initial state passed to the action upon the first iteration. + /// Period for running the work periodically. + /// Action to be executed, potentially updating the state. + /// The disposable object used to cancel the scheduled recurring action (best effort). + /// is null. + /// is less than zero. + public IDisposable SchedulePeriodic(TState state, TimeSpan period, Func action) + { + if (period < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(period)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (period == TimeSpan.Zero) + { + return new FastPeriodicTimer(state, action); + } + + return new PeriodicTimer(state, period, action); + } + + private sealed class FastPeriodicTimer : IDisposable + { + private TState _state; + private Func _action; + private volatile bool _disposed; + + public FastPeriodicTimer(TState state, Func action) + { + _state = state; + _action = action; + + ThreadPool.QueueUserWorkItem(static @this => Tick(@this!), this); // Replace with method group as soon as Roslyn will cache the delegate then. + } + + private static void Tick(object state) + { + var timer = (FastPeriodicTimer)state; + + if (!timer._disposed) + { + timer._state = timer._action(timer._state); + ThreadPool.QueueUserWorkItem(static t => Tick(t!), timer); + } + } + + public void Dispose() + { + _disposed = true; + _action = Stubs.I; + } + } + + private sealed class PeriodicTimer : IDisposable + { + private TState _state; + private Func _action; + + private readonly AsyncLock _gate; + private volatile Timer? _timer; + + public PeriodicTimer(TState state, TimeSpan period, Func action) + { + _state = state; + _action = action; + + _gate = new AsyncLock(); + + // + // Rooting of the timer happens through the this.Tick delegate's target object, + // which is the current instance and has a field to store the Timer instance. + // + _timer = new Timer(static @this => ((PeriodicTimer)@this!).Tick(), this, period, period); + } + + private void Tick() + { + _gate.Wait( + this, + @this => + { + @this._state = @this._action(@this._state); + }); + } + + public void Dispose() + { + var timer = _timer; + if (timer != null) + { + _action = Stubs.I; + _timer = null; + + timer.Dispose(); + _gate.Dispose(); + } + } + } + } +} +#endif diff --git a/LibExternal/System.Reactive/Concurrency/UserWorkItem.cs b/LibExternal/System.Reactive/Concurrency/UserWorkItem.cs new file mode 100644 index 0000000..d5f7cd7 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/UserWorkItem.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; + +namespace System.Reactive.Concurrency +{ + internal sealed class UserWorkItem : IDisposable + { + private SingleAssignmentDisposableValue _cancelRunDisposable; + private SingleAssignmentDisposableValue _cancelQueueDisposable; + + private readonly TState _state; + private readonly IScheduler _scheduler; + private readonly Func _action; + + public UserWorkItem(IScheduler scheduler, TState state, Func action) + { + _state = state; + _action = action; + _scheduler = scheduler; + } + + public void Run() + { + if (!_cancelRunDisposable.IsDisposed) + { + _cancelRunDisposable.Disposable = _action(_scheduler, _state); + } + } + + public IDisposable CancelQueueDisposable + { + set => _cancelQueueDisposable.Disposable = value; + } + + public void Dispose() + { + _cancelQueueDisposable.Dispose(); + _cancelRunDisposable.Dispose(); + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/VirtualTimeScheduler.Extensions.cs b/LibExternal/System.Reactive/Concurrency/VirtualTimeScheduler.Extensions.cs new file mode 100644 index 0000000..4efe949 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/VirtualTimeScheduler.Extensions.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; + +namespace System.Reactive.Concurrency +{ + /// + /// Provides a set of extension methods for virtual time scheduling. + /// + public static class VirtualTimeSchedulerExtensions + { + /// + /// Schedules an action to be executed at . + /// + /// Absolute time representation type. + /// Relative time representation type. + /// Scheduler to execute the action on. + /// Relative time after which to execute the action. + /// Action to be executed. + /// The disposable object used to cancel the scheduled action (best effort). + /// or is null. + public static IDisposable ScheduleRelative(this VirtualTimeSchedulerBase scheduler, TRelative dueTime, Action action) + where TAbsolute : IComparable + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + // As stated in Scheduler.Simple.cs, + // an anonymous delegate will allow delegate caching. + // Watch https://github.com/dotnet/roslyn/issues/5835 for compiler + // support for caching delegates from method groups. + return scheduler.ScheduleRelative(action, dueTime, static (_, a) => Invoke(a)); + } + + /// + /// Schedules an action to be executed at . + /// + /// Absolute time representation type. + /// Relative time representation type. + /// Scheduler to execute the action on. + /// Absolute time at which to execute the action. + /// Action to be executed. + /// The disposable object used to cancel the scheduled action (best effort). + /// or is null. + public static IDisposable ScheduleAbsolute(this VirtualTimeSchedulerBase scheduler, TAbsolute dueTime, Action action) + where TAbsolute : IComparable + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return scheduler.ScheduleAbsolute(action, dueTime, static (_, a) => Invoke(a)); + } + + private static IDisposable Invoke(Action action) + { + action(); + return Disposable.Empty; + } + } +} diff --git a/LibExternal/System.Reactive/Concurrency/VirtualTimeScheduler.cs b/LibExternal/System.Reactive/Concurrency/VirtualTimeScheduler.cs new file mode 100644 index 0000000..a5fa0d7 --- /dev/null +++ b/LibExternal/System.Reactive/Concurrency/VirtualTimeScheduler.cs @@ -0,0 +1,451 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Globalization; + +namespace System.Reactive.Concurrency +{ + /// + /// Base class for virtual time schedulers. + /// + /// Absolute time representation type. + /// Relative time representation type. + public abstract class VirtualTimeSchedulerBase : IScheduler, IServiceProvider, IStopwatchProvider + where TAbsolute : IComparable + { + /// + /// Creates a new virtual time scheduler with the default value of TAbsolute as the initial clock value. + /// + protected VirtualTimeSchedulerBase() + : this(default!, Comparer.Default) + { + // + // NB: We allow a default value for TAbsolute here, which typically is a struct. For compat reasons, we can't + // add a generic constraint (either struct or, better, new()), and maybe a derived class has handled null + // in all abstract methods. + // + } + + /// + /// Creates a new virtual time scheduler with the specified initial clock value and absolute time comparer. + /// + /// Initial value for the clock. + /// Comparer to determine causality of events based on absolute time. + /// is null. + protected VirtualTimeSchedulerBase(TAbsolute initialClock, IComparer comparer) + { + Clock = initialClock; + Comparer = comparer ?? throw new ArgumentNullException(nameof(comparer)); + } + + /// + /// Adds a relative time value to an absolute time value. + /// + /// Absolute time value. + /// Relative time value to add. + /// The resulting absolute time sum value. + protected abstract TAbsolute Add(TAbsolute absolute, TRelative relative); + + /// + /// Converts the absolute time value to a DateTimeOffset value. + /// + /// Absolute time value to convert. + /// The corresponding DateTimeOffset value. + protected abstract DateTimeOffset ToDateTimeOffset(TAbsolute absolute); + + /// + /// Converts the TimeSpan value to a relative time value. + /// + /// TimeSpan value to convert. + /// The corresponding relative time value. + protected abstract TRelative ToRelative(TimeSpan timeSpan); + + /// + /// Gets whether the scheduler is enabled to run work. + /// + public bool IsEnabled { get; private set; } + + /// + /// Gets the comparer used to compare absolute time values. + /// + protected IComparer Comparer { get; } + + /// + /// Schedules an action to be executed at dueTime. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Absolute time at which to execute the action. + /// Action to be executed. + /// The disposable object used to cancel the scheduled action (best effort). + public abstract IDisposable ScheduleAbsolute(TState state, TAbsolute dueTime, Func action); + + /// + /// Schedules an action to be executed at dueTime. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Relative time after which to execute the action. + /// Action to be executed. + /// The disposable object used to cancel the scheduled action (best effort). + public IDisposable ScheduleRelative(TState state, TRelative dueTime, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var runAt = Add(Clock, dueTime); + + return ScheduleAbsolute(state, runAt, action); + } + + /// + /// Schedules an action to be executed. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public IDisposable Schedule(TState state, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return ScheduleAbsolute(state, Clock, action); + } + + /// + /// Schedules an action to be executed after dueTime. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Relative time after which to execute the action. + /// Action to be executed. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public IDisposable Schedule(TState state, TimeSpan dueTime, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return ScheduleRelative(state, ToRelative(dueTime), action); + } + + /// + /// Schedules an action to be executed at dueTime. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Absolute time at which to execute the action. + /// Action to be executed. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public IDisposable Schedule(TState state, DateTimeOffset dueTime, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return ScheduleRelative(state, ToRelative(dueTime - Now), action); + } + + /// + /// Starts the virtual time scheduler. + /// + public void Start() + { + if (!IsEnabled) + { + IsEnabled = true; + do + { + var next = GetNext(); + if (next != null) + { + if (Comparer.Compare(next.DueTime, Clock) > 0) + { + Clock = next.DueTime; + } + + next.Invoke(); + } + else + { + IsEnabled = false; + } + } while (IsEnabled); + } + } + + /// + /// Stops the virtual time scheduler. + /// + public void Stop() + { + IsEnabled = false; + } + + /// + /// Advances the scheduler's clock to the specified time, running all work till that point. + /// + /// Absolute time to advance the scheduler's clock to. + /// is in the past. + /// The scheduler is already running. VirtualTimeScheduler doesn't support running nested work dispatch loops. To simulate time slippage while running work on the scheduler, use . + public void AdvanceTo(TAbsolute time) + { + var dueToClock = Comparer.Compare(time, Clock); + if (dueToClock < 0) + { + throw new ArgumentOutOfRangeException(nameof(time)); + } + + if (dueToClock == 0) + { + return; + } + + if (!IsEnabled) + { + IsEnabled = true; + do + { + var next = GetNext(); + if (next != null && Comparer.Compare(next.DueTime, time) <= 0) + { + if (Comparer.Compare(next.DueTime, Clock) > 0) + { + Clock = next.DueTime; + } + + next.Invoke(); + } + else + { + IsEnabled = false; + } + } while (IsEnabled); + + Clock = time; + } + else + { + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings_Linq.CANT_ADVANCE_WHILE_RUNNING, nameof(AdvanceTo))); + } + } + + /// + /// Advances the scheduler's clock by the specified relative time, running all work scheduled for that timespan. + /// + /// Relative time to advance the scheduler's clock by. + /// is negative. + /// The scheduler is already running. VirtualTimeScheduler doesn't support running nested work dispatch loops. To simulate time slippage while running work on the scheduler, use . + public void AdvanceBy(TRelative time) + { + var dt = Add(Clock, time); + + var dueToClock = Comparer.Compare(dt, Clock); + if (dueToClock < 0) + { + throw new ArgumentOutOfRangeException(nameof(time)); + } + + if (dueToClock == 0) + { + return; + } + + if (!IsEnabled) + { + AdvanceTo(dt); + } + else + { + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings_Linq.CANT_ADVANCE_WHILE_RUNNING, nameof(AdvanceBy))); + } + } + + /// + /// Advances the scheduler's clock by the specified relative time. + /// + /// Relative time to advance the scheduler's clock by. + /// is negative. + public void Sleep(TRelative time) + { + var dt = Add(Clock, time); + + var dueToClock = Comparer.Compare(dt, Clock); + if (dueToClock < 0) + { + throw new ArgumentOutOfRangeException(nameof(time)); + } + + Clock = dt; + } + + /// + /// Gets the scheduler's absolute time clock value. + /// + public TAbsolute Clock + { + get; + protected set; + } + + /// + /// Gets the scheduler's notion of current time. + /// + public DateTimeOffset Now => ToDateTimeOffset(Clock); + + /// + /// Gets the next scheduled item to be executed. + /// + /// The next scheduled item. + protected abstract IScheduledItem? GetNext(); + + object? IServiceProvider.GetService(Type serviceType) => GetService(serviceType); + + /// + /// Discovers scheduler services by interface type. The base class implementation supports + /// only the IStopwatchProvider service. To influence service discovery - such as adding + /// support for other scheduler services - derived types can override this method. + /// + /// Scheduler service interface type to discover. + /// Object implementing the requested service, if available; null otherwise. + protected virtual object? GetService(Type serviceType) + { + if (serviceType == typeof(IStopwatchProvider)) + { + return this; + } + + return null; + } + + /// + /// Starts a new stopwatch object. + /// + /// New stopwatch object; started at the time of the request. + public IStopwatch StartStopwatch() + { + var start = ClockToDateTimeOffset(); + return new VirtualTimeStopwatch(this, start); + } + + private DateTimeOffset ClockToDateTimeOffset() => ToDateTimeOffset(Clock); + + private sealed class VirtualTimeStopwatch : IStopwatch + { + private readonly VirtualTimeSchedulerBase _parent; + private readonly DateTimeOffset _start; + + public VirtualTimeStopwatch(VirtualTimeSchedulerBase parent, DateTimeOffset start) + { + _parent = parent; + _start = start; + } + + public TimeSpan Elapsed => _parent.ClockToDateTimeOffset() - _start; + } + } + + /// + /// Base class for virtual time schedulers using a priority queue for scheduled items. + /// + /// Absolute time representation type. + /// Relative time representation type. + public abstract class VirtualTimeScheduler : VirtualTimeSchedulerBase + where TAbsolute : IComparable + { + private readonly SchedulerQueue _queue = new(); + + /// + /// Creates a new virtual time scheduler with the default value of TAbsolute as the initial clock value. + /// + protected VirtualTimeScheduler() + { + } + + /// + /// Creates a new virtual time scheduler. + /// + /// Initial value for the clock. + /// Comparer to determine causality of events based on absolute time. + /// is null. + protected VirtualTimeScheduler(TAbsolute initialClock, IComparer comparer) + : base(initialClock, comparer) + { + } + + /// + /// Gets the next scheduled item to be executed. + /// + /// The next scheduled item. + protected override IScheduledItem? GetNext() + { + lock (_queue) + { + while (_queue.Count > 0) + { + var next = _queue.Peek(); + if (next.IsCanceled) + { + _queue.Dequeue(); + } + else + { + return next; + } + } + } + + return null; + } + + /// + /// Schedules an action to be executed at dueTime. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// Absolute time at which to execute the action. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public override IDisposable ScheduleAbsolute(TState state, TAbsolute dueTime, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + ScheduledItem? si = null; + + var run = new Func((scheduler, state1) => + { + lock (_queue) + { + _queue.Remove(si!); // NB: Assigned before function is invoked. + } + + return action(scheduler, state1); + }); + + si = new ScheduledItem(this, state, run, dueTime, Comparer); + + lock (_queue) + { + _queue.Enqueue(si); + } + + return si; + } + } +} diff --git a/LibExternal/System.Reactive/Diagnostics/CodeAnalysis/NullableAttributes.cs b/LibExternal/System.Reactive/Diagnostics/CodeAnalysis/NullableAttributes.cs new file mode 100644 index 0000000..d721561 --- /dev/null +++ b/LibExternal/System.Reactive/Diagnostics/CodeAnalysis/NullableAttributes.cs @@ -0,0 +1,152 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if NO_NULLABLE_ATTRIBUTES + +namespace System.Diagnostics.CodeAnalysis +{ + /// Specifies that null is allowed as an input even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] + internal sealed class AllowNullAttribute : Attribute + { } + + /// Specifies that null is disallowed as an input even if the corresponding type allows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] + internal sealed class DisallowNullAttribute : Attribute + { } + + /// Specifies that an output may be null even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] + internal sealed class MaybeNullAttribute : Attribute + { } + + /// Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input argument was not null when the call returns. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] + internal sealed class NotNullAttribute : Attribute + { } + + /// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class MaybeNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter may be null. + /// + public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } + } + + /// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class NotNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } + } + + /// Specifies that the output will be non-null if the named parameter is non-null. + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] + internal sealed class NotNullIfNotNullAttribute : Attribute + { + /// Initializes the attribute with the associated parameter name. + /// + /// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null. + /// + public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; + + /// Gets the associated parameter name. + public string ParameterName { get; } + } + + /// Applied to a method that will never return under any circumstance. + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + internal sealed class DoesNotReturnAttribute : Attribute + { } + + /// Specifies that the method will not return if the associated Boolean parameter is passed the specified value. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class DoesNotReturnIfAttribute : Attribute + { + /// Initializes the attribute with the specified parameter value. + /// + /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to + /// the associated parameter matches this value. + /// + public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; + + /// Gets the condition parameter value. + public bool ParameterValue { get; } + } + + /// Specifies that the method or property will ensure that the listed field and property members have not-null values. + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] + internal sealed class MemberNotNullAttribute : Attribute + { + /// Initializes the attribute with a field or property member. + /// + /// The field or property member that is promised to be not-null. + /// +#pragma warning disable CA1019 // Define accessors for attribute arguments - this needs to be identical to the real type + public MemberNotNullAttribute(string member) => Members = new[] { member }; +#pragma warning restore CA1019 + + /// Initializes the attribute with the list of field and property members. + /// + /// The list of field and property members that are promised to be not-null. + /// + public MemberNotNullAttribute(params string[] members) => Members = members; + + /// Gets field or property member names. + public string[] Members { get; } + } + + /// Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition. + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] + internal sealed class MemberNotNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition and a field or property member. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + /// + /// The field or property member that is promised to be not-null. + /// +#pragma warning disable CA1019 // Define accessors for attribute arguments - this needs to be identical to the real type + public MemberNotNullWhenAttribute(bool returnValue, string member) +#pragma warning restore CA1019 + { + ReturnValue = returnValue; + Members = new[] { member }; + } + + /// Initializes the attribute with the specified return value condition and list of field and property members. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + /// + /// The list of field and property members that are promised to be not-null. + /// + public MemberNotNullWhenAttribute(bool returnValue, params string[] members) + { + ReturnValue = returnValue; + Members = members; + } + + /// Gets the return value condition. + public bool ReturnValue { get; } + + /// Gets field or property member names. + public string[] Members { get; } + } +} + +#endif diff --git a/LibExternal/System.Reactive/Disposables/AnonymousDisposable.cs b/LibExternal/System.Reactive/Disposables/AnonymousDisposable.cs new file mode 100644 index 0000000..fd4dd18 --- /dev/null +++ b/LibExternal/System.Reactive/Disposables/AnonymousDisposable.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Threading; + +namespace System.Reactive.Disposables +{ + /// + /// Represents an Action-based disposable. + /// + internal sealed class AnonymousDisposable : ICancelable + { + private volatile Action? _dispose; + + /// + /// Constructs a new disposable with the given action used for disposal. + /// + /// Disposal action which will be run upon calling Dispose. + public AnonymousDisposable(Action dispose) + { + Diagnostics.Debug.Assert(dispose != null); + + _dispose = dispose; + } + + /// + /// Gets a value that indicates whether the object is disposed. + /// + public bool IsDisposed => _dispose == null; + + /// + /// Calls the disposal action if and only if the current instance hasn't been disposed yet. + /// + public void Dispose() + { + Interlocked.Exchange(ref _dispose, null)?.Invoke(); + } + } + + /// + /// Represents a Action-based disposable that can hold onto some state. + /// + internal sealed class AnonymousDisposable : ICancelable + { + private TState _state; + private volatile Action? _dispose; + + /// + /// Constructs a new disposable with the given action used for disposal. + /// + /// The state to be passed to the disposal action. + /// Disposal action which will be run upon calling Dispose. + public AnonymousDisposable(TState state, Action dispose) + { + Diagnostics.Debug.Assert(dispose != null); + + _state = state; + _dispose = dispose; + } + + /// + /// Gets a value that indicates whether the object is disposed. + /// + public bool IsDisposed => _dispose == null; + + /// + /// Calls the disposal action if and only if the current instance hasn't been disposed yet. + /// + public void Dispose() + { + Interlocked.Exchange(ref _dispose, null)?.Invoke(_state); + _state = default!; + } + } +} diff --git a/LibExternal/System.Reactive/Disposables/BooleanDisposable.cs b/LibExternal/System.Reactive/Disposables/BooleanDisposable.cs new file mode 100644 index 0000000..097f973 --- /dev/null +++ b/LibExternal/System.Reactive/Disposables/BooleanDisposable.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Disposables +{ + /// + /// Represents a disposable resource that can be checked for disposal status. + /// + public sealed class BooleanDisposable : ICancelable + { + // Keep internal! This is used as sentinel in other IDisposable implementations to detect disposal and + // should never be exposed to user code in order to prevent users from swapping in the sentinel. Have + // a look at the code in e.g. SingleAssignmentDisposable for usage patterns. + internal static readonly BooleanDisposable True = new(true); + + private volatile bool _isDisposed; + + /// + /// Initializes a new instance of the class. + /// + public BooleanDisposable() + { + } + + private BooleanDisposable(bool isDisposed) + { + _isDisposed = isDisposed; + } + + /// + /// Gets a value that indicates whether the object is disposed. + /// + public bool IsDisposed => _isDisposed; + + /// + /// Sets the status to disposed, which can be observer through the property. + /// + public void Dispose() + { + _isDisposed = true; + } + } +} diff --git a/LibExternal/System.Reactive/Disposables/CancellationDisposable.cs b/LibExternal/System.Reactive/Disposables/CancellationDisposable.cs new file mode 100644 index 0000000..353a263 --- /dev/null +++ b/LibExternal/System.Reactive/Disposables/CancellationDisposable.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Threading; + +namespace System.Reactive.Disposables +{ + /// + /// Represents a disposable resource that has an associated that will be set to the cancellation requested state upon disposal. + /// + public sealed class CancellationDisposable : ICancelable + { + private readonly CancellationTokenSource _cts; + + /// + /// Initializes a new instance of the class that uses an existing . + /// + /// used for cancellation. + /// is null. + public CancellationDisposable(CancellationTokenSource cts) + { + _cts = cts ?? throw new ArgumentNullException(nameof(cts)); + } + + /// + /// Initializes a new instance of the class that uses a new . + /// + public CancellationDisposable() + : this(new CancellationTokenSource()) + { + } + + /// + /// Gets the used by this . + /// + public CancellationToken Token => _cts.Token; + + /// + /// Cancels the underlying . + /// + public void Dispose() => _cts.Cancel(); + + /// + /// Gets a value that indicates whether the object is disposed. + /// + public bool IsDisposed => _cts.IsCancellationRequested; + } +} diff --git a/LibExternal/System.Reactive/Disposables/CompositeDisposable.cs b/LibExternal/System.Reactive/Disposables/CompositeDisposable.cs new file mode 100644 index 0000000..03d4abe --- /dev/null +++ b/LibExternal/System.Reactive/Disposables/CompositeDisposable.cs @@ -0,0 +1,447 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Collections.Generic; +using System.Threading; + +namespace System.Reactive.Disposables +{ + /// + /// Represents a group of disposable resources that are disposed together. + /// + [Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "Backward compat + ideally want to get rid of the ICollection nature of the type.")] + public sealed class CompositeDisposable : ICollection, ICancelable + { + private readonly object _gate = new(); + private bool _disposed; + private List _disposables; + private int _count; + private const int ShrinkThreshold = 64; + + // Default initial capacity of the _disposables list in case + // The number of items is not known upfront + private const int DefaultCapacity = 16; + + /// + /// Initializes a new instance of the class with no disposables contained by it initially. + /// + public CompositeDisposable() + { + _disposables = new List(); + } + + /// + /// Initializes a new instance of the class with the specified number of disposables. + /// + /// The number of disposables that the new CompositeDisposable can initially store. + /// is less than zero. + public CompositeDisposable(int capacity) + { + if (capacity < 0) + { + throw new ArgumentOutOfRangeException(nameof(capacity)); + } + + _disposables = new List(capacity); + } + + /// + /// Initializes a new instance of the class from a group of disposables. + /// + /// Disposables that will be disposed together. + /// is null. + /// Any of the disposables in the collection is null. + public CompositeDisposable(params IDisposable[] disposables) + { + if (disposables == null) + { + throw new ArgumentNullException(nameof(disposables)); + } + + _disposables = ToList(disposables); + + // _count can be read by other threads and thus should be properly visible + // also releases the _disposables contents so it becomes thread-safe + Volatile.Write(ref _count, _disposables.Count); + } + + /// + /// Initializes a new instance of the class from a group of disposables. + /// + /// Disposables that will be disposed together. + /// is null. + /// Any of the disposables in the collection is null. + public CompositeDisposable(IEnumerable disposables) + { + if (disposables == null) + { + throw new ArgumentNullException(nameof(disposables)); + } + + _disposables = ToList(disposables); + + // _count can be read by other threads and thus should be properly visible + // also releases the _disposables contents so it becomes thread-safe + Volatile.Write(ref _count, _disposables.Count); + } + + private static List ToList(IEnumerable disposables) + { + var capacity = disposables switch + { + IDisposable[] a => a.Length, + ICollection c => c.Count, + _ => DefaultCapacity + }; + + var list = new List(capacity); + + // do the copy and null-check in one step to avoid a + // second loop for just checking for null items + foreach (var d in disposables) + { + if (d == null) + { + throw new ArgumentException(Strings_Core.DISPOSABLES_CANT_CONTAIN_NULL, nameof(disposables)); + } + + list.Add(d); + } + + return list; + } + + /// + /// Gets the number of disposables contained in the . + /// + public int Count => Volatile.Read(ref _count); + + /// + /// Adds a disposable to the or disposes the disposable if the is disposed. + /// + /// Disposable to add. + /// is null. + public void Add(IDisposable item) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + lock (_gate) + { + if (!_disposed) + { + _disposables.Add(item); + + // If read atomically outside the lock, it should be written atomically inside + // the plain read on _count is fine here because manipulation always happens + // from inside a lock. + Volatile.Write(ref _count, _count + 1); + return; + } + } + + item.Dispose(); + } + + /// + /// Removes and disposes the first occurrence of a disposable from the . + /// + /// Disposable to remove. + /// true if found; false otherwise. + /// is null. + public bool Remove(IDisposable item) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + lock (_gate) + { + // this composite was already disposed and if the item was in there + // it has been already removed/disposed + if (_disposed) + { + return false; + } + + // + // List doesn't shrink the size of the underlying array but does collapse the array + // by copying the tail one position to the left of the removal index. We don't need + // index-based lookup but only ordering for sequential disposal. So, instead of spending + // cycles on the Array.Copy imposed by Remove, we use a null sentinel value. We also + // do manual Swiss cheese detection to shrink the list if there's a lot of holes in it. + // + + // read fields as infrequently as possible + var current = _disposables; + + var i = current.IndexOf(item); + if (i < 0) + { + // not found, just return + return false; + } + + current[i] = null; + + if (current.Capacity > ShrinkThreshold && _count < current.Capacity / 2) + { + var fresh = new List(current.Capacity / 2); + + foreach (var d in current) + { + if (d != null) + { + fresh.Add(d); + } + } + + _disposables = fresh; + } + + // make sure the Count property sees an atomic update + Volatile.Write(ref _count, _count - 1); + } + + // if we get here, the item was found and removed from the list + // just dispose it and report success + + item.Dispose(); + + return true; + } + + /// + /// Disposes all disposables in the group and removes them from the group. + /// + public void Dispose() + { + List? currentDisposables = null; + + lock (_gate) + { + if (!_disposed) + { + currentDisposables = _disposables; + + // nulling out the reference is faster no risk to + // future Add/Remove because _disposed will be true + // and thus _disposables won't be touched again. + _disposables = null!; // NB: All accesses are guarded by _disposed checks. + + Volatile.Write(ref _count, 0); + Volatile.Write(ref _disposed, true); + } + } + + if (currentDisposables != null) + { + foreach (var d in currentDisposables) + { + d?.Dispose(); + } + } + } + + /// + /// Removes and disposes all disposables from the , but does not dispose the . + /// + public void Clear() + { + IDisposable?[] previousDisposables; + + lock (_gate) + { + // disposed composites are always clear + if (_disposed) + { + return; + } + + var current = _disposables; + + previousDisposables = current.ToArray(); + current.Clear(); + + Volatile.Write(ref _count, 0); + } + + foreach (var d in previousDisposables) + { + d?.Dispose(); + } + } + + /// + /// Determines whether the contains a specific disposable. + /// + /// Disposable to search for. + /// true if the disposable was found; otherwise, false. + /// is null. + public bool Contains(IDisposable item) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + lock (_gate) + { + if (_disposed) + { + return false; + } + + return _disposables.Contains(item); + } + } + + /// + /// Copies the disposables contained in the to an array, starting at a particular array index. + /// + /// Array to copy the contained disposables to. + /// Target index at which to copy the first disposable of the group. + /// is null. + /// is less than zero. -or - is larger than or equal to the array length. + public void CopyTo(IDisposable[] array, int arrayIndex) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + if (arrayIndex < 0 || arrayIndex >= array.Length) + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + } + + lock (_gate) + { + // disposed composites are always empty + if (_disposed) + { + return; + } + + if (arrayIndex + _count > array.Length) + { + // there is not enough space beyond arrayIndex + // to accommodate all _count disposables in this composite + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + } + + var i = arrayIndex; + + foreach (var d in _disposables) + { + if (d != null) + { + array[i++] = d; + } + } + } + } + + /// + /// Always returns false. + /// + public bool IsReadOnly => false; + + /// + /// Returns an enumerator that iterates through the . + /// + /// An enumerator to iterate over the disposables. + public IEnumerator GetEnumerator() + { + lock (_gate) + { + if (_disposed || _count == 0) + { + return EmptyEnumerator; + } + + // the copy is unavoidable but the creation + // of an outer IEnumerable is avoidable + return new CompositeEnumerator(_disposables.ToArray()); + } + } + + /// + /// Returns an enumerator that iterates through the . + /// + /// An enumerator to iterate over the disposables. + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + /// Gets a value that indicates whether the object is disposed. + /// + public bool IsDisposed => Volatile.Read(ref _disposed); + + /// + /// An empty enumerator for the + /// method to avoid allocation on disposed or empty composites. + /// + private static readonly CompositeEnumerator EmptyEnumerator = + new(Array.Empty()); + + /// + /// An enumerator for an array of disposables. + /// + private sealed class CompositeEnumerator : IEnumerator + { + private readonly IDisposable?[] _disposables; + private int _index; + + public CompositeEnumerator(IDisposable?[] disposables) + { + _disposables = disposables; + _index = -1; + } + + public IDisposable Current => _disposables[_index]!; // NB: _index is only advanced to non-null positions. + + object IEnumerator.Current => _disposables[_index]!; + + public void Dispose() + { + // Avoid retention of the referenced disposables + // beyond the lifecycle of the enumerator. + // Not sure if this happens by default to + // generic array enumerators though. + var disposables = _disposables; + Array.Clear(disposables, 0, disposables.Length); + } + + public bool MoveNext() + { + var disposables = _disposables; + + for (; ; ) + { + var idx = ++_index; + + if (idx >= disposables.Length) + { + return false; + } + + // inlined that filter for null elements + if (disposables[idx] != null) + { + return true; + } + } + } + + public void Reset() + { + _index = -1; + } + } + } +} diff --git a/LibExternal/System.Reactive/Disposables/ContextDisposable.cs b/LibExternal/System.Reactive/Disposables/ContextDisposable.cs new file mode 100644 index 0000000..c1f2b7e --- /dev/null +++ b/LibExternal/System.Reactive/Disposables/ContextDisposable.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Threading; + +namespace System.Reactive.Disposables +{ + /// + /// Represents a disposable resource whose disposal invocation will be posted to the specified . + /// + public sealed class ContextDisposable : ICancelable + { + private volatile IDisposable _disposable; + + /// + /// Initializes a new instance of the class that uses the specified on which to dispose the specified disposable resource. + /// + /// Context to perform disposal on. + /// Disposable whose Dispose operation to run on the given synchronization context. + /// or is null. + public ContextDisposable(SynchronizationContext context, IDisposable disposable) + { + Context = context ?? throw new ArgumentNullException(nameof(context)); + _disposable = disposable ?? throw new ArgumentNullException(nameof(disposable)); + } + + /// + /// Gets the provided . + /// + public SynchronizationContext Context { get; } + + /// + /// Gets a value that indicates whether the object is disposed. + /// + public bool IsDisposed => _disposable == BooleanDisposable.True; + + /// + /// Disposes the underlying disposable on the provided . + /// + public void Dispose() + { + var old = Interlocked.Exchange(ref _disposable, BooleanDisposable.True); + + if (old != BooleanDisposable.True) + { + Context.PostWithStartComplete(static d => d.Dispose(), old); + } + } + } +} diff --git a/LibExternal/System.Reactive/Disposables/Disposable.Utils.cs b/LibExternal/System.Reactive/Disposables/Disposable.Utils.cs new file mode 100644 index 0000000..8c0b8dc --- /dev/null +++ b/LibExternal/System.Reactive/Disposables/Disposable.Utils.cs @@ -0,0 +1,142 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics.CodeAnalysis; +using System.Threading; + +namespace System.Reactive.Disposables +{ + internal enum TrySetSingleResult + { + Success, + AlreadyAssigned, + Disposed + } + + public static partial class Disposable + { + /// + /// Gets the value stored in or a null if + /// was already disposed. + /// + internal static IDisposable? GetValue([NotNullIfNotNull("fieldRef")] /*in*/ ref IDisposable? fieldRef) + { + var current = Volatile.Read(ref fieldRef); + + return current == BooleanDisposable.True + ? null + : current; + } + + /// + /// Gets the value stored in or a no-op-Disposable if + /// was already disposed. + /// + [return: NotNullIfNotNull("fieldRef")] + internal static IDisposable? GetValueOrDefault([NotNullIfNotNull("fieldRef")] /*in*/ ref IDisposable? fieldRef) + { + var current = Volatile.Read(ref fieldRef); + + return current == BooleanDisposable.True + ? EmptyDisposable.Instance + : current; + } + + /// + /// Tries to assign to . + /// + /// A value indicating the outcome of the operation. + internal static TrySetSingleResult TrySetSingle([NotNullIfNotNull("value")] ref IDisposable? fieldRef, IDisposable? value) + { + var old = Interlocked.CompareExchange(ref fieldRef, value, null); + if (old == null) + { + return TrySetSingleResult.Success; + } + + if (old != BooleanDisposable.True) + { + return TrySetSingleResult.AlreadyAssigned; + } + + value?.Dispose(); + return TrySetSingleResult.Disposed; + } + + /// + /// Tries to assign to . If + /// is not disposed and is assigned a different value, it will not be disposed. + /// + /// true if was successfully assigned to . + /// false has been disposed. + internal static bool TrySetMultiple([NotNullIfNotNull("value")] ref IDisposable? fieldRef, IDisposable? value) + { + // Let's read the current value atomically (also prevents reordering). + var old = Volatile.Read(ref fieldRef); + + for (; ; ) + { + // If it is the disposed instance, dispose the value. + if (old == BooleanDisposable.True) + { + value?.Dispose(); + return false; + } + + // Atomically swap in the new value and get back the old. + var b = Interlocked.CompareExchange(ref fieldRef, value, old); + + // If the old and new are the same, the swap was successful and we can quit + if (old == b) + { + return true; + } + + // Otherwise, make the old reference the current and retry. + old = b; + } + } + + /// + /// Tries to assign to . If + /// is not disposed and is assigned a different value, it will be disposed. + /// + /// true if was successfully assigned to . + /// false has been disposed. + internal static bool TrySetSerial([NotNullIfNotNull("value")] ref IDisposable? fieldRef, IDisposable? value) + { + var copy = Volatile.Read(ref fieldRef); + for (; ; ) + { + if (copy == BooleanDisposable.True) + { + value?.Dispose(); + return false; + } + + var current = Interlocked.CompareExchange(ref fieldRef, value, copy); + if (current == copy) + { + copy?.Dispose(); + return true; + } + + copy = current; + } + } + + /// + /// Disposes . + /// + internal static void Dispose([NotNullIfNotNull("fieldRef")] ref IDisposable? fieldRef) + { + var old = Interlocked.Exchange(ref fieldRef, BooleanDisposable.True); + + if (old != BooleanDisposable.True) + { + old?.Dispose(); + } + } + } +} diff --git a/LibExternal/System.Reactive/Disposables/Disposable.cs b/LibExternal/System.Reactive/Disposables/Disposable.cs new file mode 100644 index 0000000..db554fe --- /dev/null +++ b/LibExternal/System.Reactive/Disposables/Disposable.cs @@ -0,0 +1,75 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Threading; + +namespace System.Reactive.Disposables +{ + /// + /// Provides a set of static methods for creating objects. + /// + public static partial class Disposable + { + /// + /// Represents a disposable that does nothing on disposal. + /// + private sealed class EmptyDisposable : IDisposable + { + /// + /// Singleton default disposable. + /// + public static readonly EmptyDisposable Instance = new(); + + private EmptyDisposable() + { + } + + /// + /// Does nothing. + /// + public void Dispose() + { + // no op + } + } + + /// + /// Gets the disposable that does nothing when disposed. + /// + public static IDisposable Empty => EmptyDisposable.Instance; + + /// + /// Creates a disposable object that invokes the specified action when disposed. + /// + /// Action to run during the first call to . The action is guaranteed to be run at most once. + /// The disposable object that runs the given action upon disposal. + /// is null. + public static IDisposable Create(Action dispose) + { + if (dispose == null) + { + throw new ArgumentNullException(nameof(dispose)); + } + + return new AnonymousDisposable(dispose); + } + + /// + /// Creates a disposable object that invokes the specified action when disposed. + /// + /// The state to be passed to the action. + /// Action to run during the first call to . The action is guaranteed to be run at most once. + /// The disposable object that runs the given action upon disposal. + /// is null. + public static IDisposable Create(TState state, Action dispose) + { + if (dispose == null) + { + throw new ArgumentNullException(nameof(dispose)); + } + + return new AnonymousDisposable(state, dispose); + } + } +} diff --git a/LibExternal/System.Reactive/Disposables/ICancelable.cs b/LibExternal/System.Reactive/Disposables/ICancelable.cs new file mode 100644 index 0000000..585b193 --- /dev/null +++ b/LibExternal/System.Reactive/Disposables/ICancelable.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Disposables +{ + /// + /// Disposable resource with disposal state tracking. + /// + public interface ICancelable : IDisposable + { + /// + /// Gets a value that indicates whether the object is disposed. + /// + bool IsDisposed { get; } + } +} diff --git a/LibExternal/System.Reactive/Disposables/MultipleAssignmentDisposable.cs b/LibExternal/System.Reactive/Disposables/MultipleAssignmentDisposable.cs new file mode 100644 index 0000000..9a2e499 --- /dev/null +++ b/LibExternal/System.Reactive/Disposables/MultipleAssignmentDisposable.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Disposables +{ + /// + /// Represents a disposable resource whose underlying disposable resource can be swapped for another disposable resource. + /// + public sealed class MultipleAssignmentDisposable : ICancelable + { + private MultipleAssignmentDisposableValue _current; + + /// + /// Initializes a new instance of the class with no current underlying disposable. + /// + public MultipleAssignmentDisposable() + { + } + + /// + /// Gets a value that indicates whether the object is disposed. + /// + public bool IsDisposed => _current.IsDisposed; + + /// + /// Gets or sets the underlying disposable. After disposal, the result of getting this property is undefined. + /// + /// If the has already been disposed, assignment to this property causes immediate disposal of the given disposable object. + public IDisposable? Disposable + { + get => _current.Disposable; + set => _current.Disposable = value; + } + + /// + /// Disposes the underlying disposable as well as all future replacements. + /// + public void Dispose() + { + _current.Dispose(); + } + } +} diff --git a/LibExternal/System.Reactive/Disposables/MultipleAssignmentDisposableValue.cs b/LibExternal/System.Reactive/Disposables/MultipleAssignmentDisposableValue.cs new file mode 100644 index 0000000..b3f211b --- /dev/null +++ b/LibExternal/System.Reactive/Disposables/MultipleAssignmentDisposableValue.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Threading; + +namespace System.Reactive.Disposables +{ + /// + /// Represents a disposable resource whose underlying disposable resource can be swapped for another disposable resource. + /// + internal struct MultipleAssignmentDisposableValue : ICancelable + { + private IDisposable? _current; + + /// + /// Gets a value that indicates whether the object is disposed. + /// + public bool IsDisposed => + // We use a sentinel value to indicate we've been disposed. This sentinel never leaks + // to the outside world (see the Disposable property getter), so no-one can ever assign + // this value to us manually. + Volatile.Read(ref _current) == BooleanDisposable.True; + + /// + /// Gets or sets the underlying disposable. After disposal, the result of getting this property is undefined. + /// + /// If the has already been disposed, assignment to this property causes immediate disposal of the given disposable object. + public IDisposable? Disposable + { + get => Disposables.Disposable.GetValueOrDefault(ref _current); + set => Disposables.Disposable.TrySetMultiple(ref _current, value); + } + + public bool TrySetFirst(IDisposable disposable) => Disposables.Disposable.TrySetSingle(ref _current, disposable) == TrySetSingleResult.Success; + + /// + /// Disposes the underlying disposable as well as all future replacements. + /// + public void Dispose() + { + Disposables.Disposable.Dispose(ref _current); + } + } +} diff --git a/LibExternal/System.Reactive/Disposables/RefCountDisposable.cs b/LibExternal/System.Reactive/Disposables/RefCountDisposable.cs new file mode 100644 index 0000000..839e17e --- /dev/null +++ b/LibExternal/System.Reactive/Disposables/RefCountDisposable.cs @@ -0,0 +1,182 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Threading; + +namespace System.Reactive.Disposables +{ + /// + /// Represents a disposable resource that only disposes its underlying disposable resource when all dependent disposable objects have been disposed. + /// + public sealed class RefCountDisposable : ICancelable + { + private readonly bool _throwWhenDisposed; + + private IDisposable? _disposable; + + /// + /// Holds the number of active child disposables and the + /// indicator bit (31) if the main _disposable has been marked + /// for disposition. + /// + private int _count; + + /// + /// Initializes a new instance of the class with the specified disposable. + /// + /// Underlying disposable. + /// is null. + public RefCountDisposable(IDisposable disposable) : this(disposable, false) + { + } + + /// + /// Initializes a new instance of the class with the specified disposable. + /// + /// Underlying disposable. + /// Indicates whether subsequent calls to should throw when this instance is disposed. + /// is null. + public RefCountDisposable(IDisposable disposable, bool throwWhenDisposed) + { + _disposable = disposable ?? throw new ArgumentNullException(nameof(disposable)); + _count = 0; + _throwWhenDisposed = throwWhenDisposed; + } + + /// + /// Gets a value that indicates whether the object is disposed. + /// + public bool IsDisposed => Volatile.Read(ref _count) == int.MinValue; + + /// + /// Returns a dependent disposable that when disposed decreases the refcount on the underlying disposable. + /// + /// A dependent disposable contributing to the reference count that manages the underlying disposable's lifetime. + /// This instance has been disposed and is configured to throw in this case by . + public IDisposable GetDisposable() + { + // the current state + var cnt = Volatile.Read(ref _count); + + for (; ; ) + { + // If bit 31 is set and the active count is zero, don't create an inner + if (cnt == int.MinValue) + { + if (_throwWhenDisposed) + { + throw new ObjectDisposedException("RefCountDisposable"); + } + + return Disposable.Empty; + } + + // Should not overflow the bits 0..30 + if ((cnt & 0x7FFFFFFF) == int.MaxValue) + { + throw new OverflowException($"RefCountDisposable can't handle more than {int.MaxValue} disposables"); + } + + // Increment the active count by one, works because the increment + // won't affect bit 31 + var u = Interlocked.CompareExchange(ref _count, cnt + 1, cnt); + if (u == cnt) + { + return new InnerDisposable(this); + } + cnt = u; + } + } + + /// + /// Disposes the underlying disposable only when all dependent disposables have been disposed. + /// + public void Dispose() + { + var cnt = Volatile.Read(ref _count); + + for (; ; ) + { + + // already marked as disposed via bit 31? + if ((cnt & 0x80000000) != 0) + { + // yes, nothing to do + break; + } + + // how many active disposables are there? + var active = cnt & 0x7FFFFFFF; + + // keep the active count but set the dispose marker of bit 31 + var u = int.MinValue | active; + + var b = Interlocked.CompareExchange(ref _count, u, cnt); + + if (b == cnt) + { + // if there were 0 active disposables, there can't be any more after + // the CAS so we can dispose the underlying disposable + if (active == 0) + { + _disposable?.Dispose(); + _disposable = null; + } + break; + } + cnt = b; + } + } + + private void Release() + { + var cnt = Volatile.Read(ref _count); + + for (; ; ) + { + // extract the main disposed state (bit 31) + var main = (int)(cnt & 0x80000000); + // get the active count + var active = cnt & 0x7FFFFFFF; + + // keep the main disposed state but decrement the counter + // in theory, active should be always > 0 at this point, + // guaranteed by the InnerDisposable.Dispose's Exchange operation. + Diagnostics.Debug.Assert(active > 0); + var u = main | (active - 1); + + var b = Interlocked.CompareExchange(ref _count, u, cnt); + + if (b == cnt) + { + // if after the CAS there was zero active disposables and + // the main has been also marked for disposing, + // it is safe to dispose the underlying disposable + if (u == int.MinValue) + { + _disposable?.Dispose(); + _disposable = null; + } + break; + } + cnt = b; + } + } + + private sealed class InnerDisposable : IDisposable + { + private RefCountDisposable? _parent; + + public InnerDisposable(RefCountDisposable parent) + { + _parent = parent; + } + + public void Dispose() + { + Interlocked.Exchange(ref _parent, null)?.Release(); + } + } + } +} diff --git a/LibExternal/System.Reactive/Disposables/ScheduledDisposable.cs b/LibExternal/System.Reactive/Disposables/ScheduledDisposable.cs new file mode 100644 index 0000000..f6bde3c --- /dev/null +++ b/LibExternal/System.Reactive/Disposables/ScheduledDisposable.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; + +namespace System.Reactive.Disposables +{ + /// + /// Represents a disposable resource whose disposal invocation will be scheduled on the specified . + /// + public sealed class ScheduledDisposable : ICancelable + { + private SingleAssignmentDisposableValue _disposable; + + /// + /// Initializes a new instance of the class that uses an on which to dispose the disposable. + /// + /// Scheduler where the disposable resource will be disposed on. + /// Disposable resource to dispose on the given scheduler. + /// or is null. + public ScheduledDisposable(IScheduler scheduler, IDisposable disposable) + { + Scheduler = scheduler ?? throw new ArgumentNullException(nameof(scheduler)); + _disposable.Disposable = disposable ?? throw new ArgumentNullException(nameof(disposable)); + } + + /// + /// Gets the scheduler where the disposable resource will be disposed on. + /// + public IScheduler Scheduler { get; } + + /// + /// Gets the underlying disposable. After disposal, the result is undefined. + /// + public IDisposable Disposable => _disposable.Disposable ?? Disposables.Disposable.Empty; + + /// + /// Gets a value that indicates whether the object is disposed. + /// + public bool IsDisposed => _disposable.IsDisposed; + + /// + /// Disposes the wrapped disposable on the provided scheduler. + /// + public void Dispose() => Scheduler.ScheduleAction(this, scheduler => scheduler._disposable.Dispose()); + } +} diff --git a/LibExternal/System.Reactive/Disposables/SerialDisposable.cs b/LibExternal/System.Reactive/Disposables/SerialDisposable.cs new file mode 100644 index 0000000..adc66ab --- /dev/null +++ b/LibExternal/System.Reactive/Disposables/SerialDisposable.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Disposables +{ + /// + /// Represents a disposable resource whose underlying disposable resource can be replaced by another disposable resource, causing automatic disposal of the previous underlying disposable resource. + /// + public sealed class SerialDisposable : ICancelable + { + private SerialDisposableValue _current; + + /// + /// Initializes a new instance of the class. + /// + public SerialDisposable() + { + } + + /// + /// Gets a value that indicates whether the object is disposed. + /// + public bool IsDisposed => _current.IsDisposed; + + /// + /// Gets or sets the underlying disposable. + /// + /// If the SerialDisposable has already been disposed, assignment to this property causes immediate disposal of the given disposable object. Assigning this property disposes the previous disposable object. + public IDisposable? Disposable + { + get => _current.Disposable; + set => _current.Disposable = value; + } + + /// + /// Disposes the underlying disposable as well as all future replacements. + /// + public void Dispose() + { + _current.Dispose(); + } + } +} diff --git a/LibExternal/System.Reactive/Disposables/SerialDisposableValue.cs b/LibExternal/System.Reactive/Disposables/SerialDisposableValue.cs new file mode 100644 index 0000000..7ff6e93 --- /dev/null +++ b/LibExternal/System.Reactive/Disposables/SerialDisposableValue.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Threading; + +namespace System.Reactive.Disposables +{ + /// + /// Represents a disposable resource whose underlying disposable resource can be replaced by another disposable resource, causing automatic disposal of the previous underlying disposable resource. + /// + internal struct SerialDisposableValue : ICancelable + { + private IDisposable? _current; + + /// + /// Gets a value that indicates whether the object is disposed. + /// + public bool IsDisposed => + // We use a sentinel value to indicate we've been disposed. This sentinel never leaks + // to the outside world (see the Disposable property getter), so no-one can ever assign + // this value to us manually. + Volatile.Read(ref _current) == BooleanDisposable.True; + + /// + /// Gets or sets the underlying disposable. + /// + /// If the SerialDisposable has already been disposed, assignment to this property causes immediate disposal of the given disposable object. Assigning this property disposes the previous disposable object. + public IDisposable? Disposable + { + get => Disposables.Disposable.GetValue(ref _current); + set => Disposables.Disposable.TrySetSerial(ref _current, value); + } + + public bool TrySetFirst(IDisposable disposable) => Disposables.Disposable.TrySetSingle(ref _current, disposable) == TrySetSingleResult.Success; + + /// + /// Disposes the underlying disposable as well as all future replacements. + /// + public void Dispose() + { + Disposables.Disposable.Dispose(ref _current); + } + } +} diff --git a/LibExternal/System.Reactive/Disposables/SingleAssignmentDisposable.cs b/LibExternal/System.Reactive/Disposables/SingleAssignmentDisposable.cs new file mode 100644 index 0000000..74771a1 --- /dev/null +++ b/LibExternal/System.Reactive/Disposables/SingleAssignmentDisposable.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Disposables +{ + /// + /// Represents a disposable resource which only allows a single assignment of its underlying disposable resource. + /// If an underlying disposable resource has already been set, future attempts to set the underlying disposable resource will throw an . + /// + public sealed class SingleAssignmentDisposable : ICancelable + { + private SingleAssignmentDisposableValue _current; + + /// + /// Initializes a new instance of the class. + /// + public SingleAssignmentDisposable() + { + } + + /// + /// Gets a value that indicates whether the object is disposed. + /// + public bool IsDisposed => _current.IsDisposed; + + /// + /// Gets or sets the underlying disposable. After disposal, the result of getting this property is undefined. + /// + /// Thrown if the has already been assigned to. + public IDisposable? Disposable + { + get => _current.Disposable; + set => _current.Disposable = value; + } + + /// + /// Disposes the underlying disposable. + /// + public void Dispose() + { + _current.Dispose(); + } + } +} diff --git a/LibExternal/System.Reactive/Disposables/SingleAssignmentDisposableValue.cs b/LibExternal/System.Reactive/Disposables/SingleAssignmentDisposableValue.cs new file mode 100644 index 0000000..8707062 --- /dev/null +++ b/LibExternal/System.Reactive/Disposables/SingleAssignmentDisposableValue.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Threading; + +namespace System.Reactive.Disposables +{ + /// + /// Represents a disposable resource which only allows a single assignment of its underlying disposable resource. + /// If an underlying disposable resource has already been set, future attempts to set the underlying disposable resource will throw an . + /// + public struct SingleAssignmentDisposableValue + { + private IDisposable? _current; + + /// + /// Gets a value that indicates whether the object is disposed. + /// + public bool IsDisposed => + // We use a sentinel value to indicate we've been disposed. This sentinel never leaks + // to the outside world (see the Disposable property getter), so no-one can ever assign + // this value to us manually. + Volatile.Read(ref _current) == BooleanDisposable.True; + + /// + /// Gets or sets the underlying disposable. After disposal, the result of getting this property is undefined. + /// + /// Thrown if the has already been assigned to. + public IDisposable? Disposable + { + get => Disposables.Disposable.GetValueOrDefault(ref _current); + set + { + var result = Disposables.Disposable.TrySetSingle(ref _current, value); + + if (result == TrySetSingleResult.AlreadyAssigned) + { + throw new InvalidOperationException(Strings_Core.DISPOSABLE_ALREADY_ASSIGNED); + } + } + } + + /// + /// Disposes the underlying disposable. + /// + public void Dispose() + { + Disposables.Disposable.Dispose(ref _current); + } + + /// + public override bool Equals(object? obj) => false; + + /// + public override int GetHashCode() => 0; + +#pragma warning disable IDE0060 // (Remove unused parameter.) Required part of public API + public static bool operator ==(SingleAssignmentDisposableValue left, SingleAssignmentDisposableValue right) => false; + + public static bool operator !=(SingleAssignmentDisposableValue left, SingleAssignmentDisposableValue right) => true; +#pragma warning restore IDE0060 + } +} diff --git a/LibExternal/System.Reactive/Disposables/StableCompositeDisposable.cs b/LibExternal/System.Reactive/Disposables/StableCompositeDisposable.cs new file mode 100644 index 0000000..637aae9 --- /dev/null +++ b/LibExternal/System.Reactive/Disposables/StableCompositeDisposable.cs @@ -0,0 +1,207 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Threading; + +namespace System.Reactive.Disposables +{ + /// + /// Represents a group of disposable resources that are disposed together. + /// +#pragma warning disable CA1063 // (Overridable IDisposable.) This analyzer wants us to make breaking changes to its public API, which we can't do. + public abstract class StableCompositeDisposable : ICancelable + { + /// + /// Creates a new group containing two disposable resources that are disposed together. + /// + /// The first disposable resource to add to the group. + /// The second disposable resource to add to the group. + /// Group of disposable resources that are disposed together. + public static ICancelable Create(IDisposable disposable1, IDisposable disposable2) + { + if (disposable1 == null) + { + throw new ArgumentNullException(nameof(disposable1)); + } + + if (disposable2 == null) + { + throw new ArgumentNullException(nameof(disposable2)); + } + + return new Binary(disposable1, disposable2); + } + + /// + /// Creates a new group of disposable resources that are disposed together. + /// + /// Disposable resources to add to the group. + /// Group of disposable resources that are disposed together. + public static ICancelable Create(params IDisposable[] disposables) + { + if (disposables == null) + { + throw new ArgumentNullException(nameof(disposables)); + } + + return new NAryArray(disposables); + } + + /// + /// Creates a group of disposable resources that are disposed together + /// and without copying or checking for nulls inside the group. + /// + /// The array of disposables that is trusted + /// to not contain nulls and gives no need to defensively copy it. + /// Group of disposable resources that are disposed together. + internal static ICancelable CreateTrusted(params IDisposable[] disposables) + { + return new NAryTrustedArray(disposables); + } + + /// + /// Creates a new group of disposable resources that are disposed together. + /// + /// Disposable resources to add to the group. + /// Group of disposable resources that are disposed together. + public static ICancelable Create(IEnumerable disposables) + { + if (disposables == null) + { + throw new ArgumentNullException(nameof(disposables)); + } + + return new NAryEnumerable(disposables); + } + + /// + /// Disposes all disposables in the group. + /// + public abstract void Dispose(); + + /// + /// Gets a value that indicates whether the object is disposed. + /// + public abstract bool IsDisposed + { + get; + } + + private sealed class Binary : StableCompositeDisposable + { + private SingleAssignmentDisposableValue _disposable1; + private SingleAssignmentDisposableValue _disposable2; + + public Binary(IDisposable disposable1, IDisposable disposable2) + { + _disposable1.Disposable = disposable1; + _disposable2.Disposable = disposable2; + } + + public override bool IsDisposed => _disposable1.IsDisposed; + + public override void Dispose() + { + _disposable1.Dispose(); + _disposable2.Dispose(); + } + } + + private sealed class NAryEnumerable : StableCompositeDisposable + { + private volatile List? _disposables; + + public NAryEnumerable(IEnumerable disposables) + { + _disposables = new List(disposables); + + // + // Doing this on the list to avoid duplicate enumeration of disposables. + // + if (_disposables.Contains(null!)) + { + throw new ArgumentException(Strings_Core.DISPOSABLES_CANT_CONTAIN_NULL, nameof(disposables)); + } + } + + public override bool IsDisposed => _disposables == null; + + public override void Dispose() + { + var old = Interlocked.Exchange(ref _disposables, null); + if (old != null) + { + foreach (var d in old) + { + d.Dispose(); + } + } + } + } + + private sealed class NAryArray : StableCompositeDisposable + { + private IDisposable[]? _disposables; + + public NAryArray(IDisposable[] disposables) + { + if (Array.IndexOf(disposables, null!) != -1) + { + throw new ArgumentException(Strings_Core.DISPOSABLES_CANT_CONTAIN_NULL, nameof(disposables)); + } + + var n = disposables.Length; + var ds = new IDisposable[n]; + + Array.Copy(disposables, 0, ds, 0, n); + + Volatile.Write(ref _disposables, ds); + } + + public override bool IsDisposed => Volatile.Read(ref _disposables) == null; + + public override void Dispose() + { + var old = Interlocked.Exchange(ref _disposables, null); + if (old != null) + { + foreach (var d in old) + { + d.Dispose(); + } + } + } + } + + /// + /// A stable composite that doesn't do defensive copy of + /// the input disposable array nor checks it for null. + /// + private sealed class NAryTrustedArray : StableCompositeDisposable + { + private IDisposable[]? _disposables; + + public NAryTrustedArray(IDisposable[] disposables) + { + Volatile.Write(ref _disposables, disposables); + } + + public override bool IsDisposed => Volatile.Read(ref _disposables) == null; + + public override void Dispose() + { + var old = Interlocked.Exchange(ref _disposables, null); + if (old != null) + { + foreach (var d in old) + { + d.Dispose(); + } + } + } + } + } +#pragma warning restore CA1063 +} diff --git a/LibExternal/System.Reactive/EnlightenmentProvider.cs b/LibExternal/System.Reactive/EnlightenmentProvider.cs new file mode 100644 index 0000000..4e6a1d9 --- /dev/null +++ b/LibExternal/System.Reactive/EnlightenmentProvider.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.PlatformServices +{ + /// + /// Provides access to the platform enlightenments used by other Rx libraries to improve system performance and + /// runtime efficiency. While Rx can run without platform enlightenments loaded, it's recommended to deploy the + /// System.Reactive.PlatformServices assembly with your application and call during + /// application startup to ensure enlightenments are properly loaded. + /// + public static class EnlightenmentProvider + { + /// + /// Ensures that the calling assembly has a reference to the System.Reactive.PlatformServices assembly with + /// platform enlightenments. If no reference is made from the user code, it's possible for the build process + /// to drop the deployment of System.Reactive.PlatformServices, preventing its runtime discovery. + /// + /// + /// true if the loaded enlightenment provider matches the provided defined in the current assembly; false + /// otherwise. When a custom enlightenment provider is installed by the host, false will be returned. + /// + public static bool EnsureLoaded() + { + return PlatformEnlightenmentProvider.Current is CurrentPlatformEnlightenmentProvider; + } + } +} diff --git a/LibExternal/System.Reactive/EventPattern.cs b/LibExternal/System.Reactive/EventPattern.cs new file mode 100644 index 0000000..00a0b00 --- /dev/null +++ b/LibExternal/System.Reactive/EventPattern.cs @@ -0,0 +1,117 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive +{ + /// + /// Represents a .NET event invocation consisting of the weakly typed object that raised the event and the data that was generated by the event. + /// + /// The type of the event data generated by the event. + public class EventPattern : EventPattern + { + /// + /// Creates a new data representation instance of a .NET event invocation with the given sender and event data. + /// + /// The sender object that raised the event. + /// The event data that was generated by the event. +#pragma warning disable CA2109 // (Consider making non-public.) This has long been part of the public API so we couldn't change this even if we wanted to. + public EventPattern(object? sender, TEventArgs e) +#pragma warning restore CA2109 + : base(sender, e) + { + } + } + + /// + /// Represents a .NET event invocation consisting of the strongly typed object that raised the event and the data that was generated by the event. + /// + /// The type of the sender that raised the event. + /// The type of the event data generated by the event. + public class EventPattern : IEquatable>, IEventPattern + { + /// + /// Creates a new data representation instance of a .NET event invocation with the given sender and event data. + /// + /// The sender object that raised the event. + /// The event data that was generated by the event. + public EventPattern(TSender? sender, TEventArgs e) + { + Sender = sender; + EventArgs = e; + } + + /// + /// Gets the sender object that raised the event. + /// + public TSender? Sender { get; } + + /// + /// Gets the event data that was generated by the event. + /// + public TEventArgs EventArgs { get; } + + /// + /// Deconstructs the event pattern value into a sender and event data. + /// + /// The sender object that raised the event. + /// The event data that was generated by the event. + public void Deconstruct(out TSender? sender, out TEventArgs e) => (sender, e) = (Sender, EventArgs); + + /// + /// Determines whether the current object represents the same event as a specified object. + /// + /// An object to compare to the current object. + /// true if both objects represent the same event; otherwise, false. + public bool Equals(EventPattern? other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return EqualityComparer.Default.Equals(Sender, other.Sender) && EqualityComparer.Default.Equals(EventArgs, other.EventArgs); + } + + /// + /// Determines whether the specified System.Object is equal to the current . + /// + /// The System.Object to compare with the current . + /// true if the specified System.Object is equal to the current ; otherwise, false. + public override bool Equals(object? obj) => Equals(obj as EventPattern); + + /// + /// Returns the hash code for the current instance. + /// + /// A hash code for the current instance. + public override int GetHashCode() + { + var x = Sender?.GetHashCode() ?? 0; + var y = EventArgs?.GetHashCode() ?? 0; + return (x << 5) + (x ^ y); + } + + /// + /// Determines whether two specified objects represent the same event. + /// + /// The first to compare, or null. + /// The second to compare, or null. + /// true if both objects represent the same event; otherwise, false. + public static bool operator ==(EventPattern first, EventPattern second) => Equals(first, second); + + /// + /// Determines whether two specified objects represent a different event. + /// + /// The first to compare, or null. + /// The second to compare, or null. + /// true if both objects don't represent the same event; otherwise, false. + public static bool operator !=(EventPattern first, EventPattern second) => !Equals(first, second); + } +} diff --git a/LibExternal/System.Reactive/EventPatternSource.cs b/LibExternal/System.Reactive/EventPatternSource.cs new file mode 100644 index 0000000..512e3e4 --- /dev/null +++ b/LibExternal/System.Reactive/EventPatternSource.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive +{ + internal sealed class EventPatternSource : EventPatternSourceBase, IEventPatternSource + { + public EventPatternSource(IObservable> source, Action, /*object,*/ EventPattern> invokeHandler) + : base(source, invokeHandler) + { + } + + event EventHandler IEventPatternSource.OnNext + { + add + { + Add(value, (o, e) => value(o, e)); + } + + remove + { + Remove(value); + } + } + } +} diff --git a/LibExternal/System.Reactive/EventPatternSourceBase.cs b/LibExternal/System.Reactive/EventPatternSourceBase.cs new file mode 100644 index 0000000..aa3647e --- /dev/null +++ b/LibExternal/System.Reactive/EventPatternSourceBase.cs @@ -0,0 +1,163 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive +{ + /// + /// Base class for classes that expose an observable sequence as a well-known event pattern (sender, event arguments). + /// Contains functionality to maintain a map of event handler delegates to observable sequence subscriptions. Subclasses + /// should only add an event with custom add and remove methods calling into the base class's operations. + /// + /// The type of the sender that raises the event. + /// The type of the event data generated by the event. + public abstract class EventPatternSourceBase + { + private sealed class Observer : ObserverBase>, ISafeObserver> + { + private bool _isDone; + private bool _isAdded; + private readonly Delegate _handler; + private readonly object _gate = new(); + private readonly Action _invoke; + private readonly EventPatternSourceBase _sourceBase; + + public Observer(EventPatternSourceBase sourceBase, Delegate handler, Action invoke) + { + _handler = handler; + _invoke = invoke; + _sourceBase = sourceBase; + } + + protected override void OnNextCore(EventPattern value) + { + _sourceBase._invokeHandler(_invoke, value); + } + + protected override void OnErrorCore(Exception error) + { + Remove(); + error.Throw(); + } + + protected override void OnCompletedCore() + { + Remove(); + } + + private void Remove() + { + lock (_gate) + { + if (_isAdded) + { + _sourceBase.Remove(_handler); + } + else + { + _isDone = true; + } + } + } + + public void SetResource(IDisposable resource) + { + lock (_gate) + { + if (!_isDone) + { + _sourceBase.Add(_handler, resource); + _isAdded = true; + } + } + } + } + + private readonly IObservable> _source; + private readonly Dictionary> _subscriptions; + private readonly Action, /*object,*/ EventPattern> _invokeHandler; + + /// + /// Creates a new event pattern source. + /// + /// Source sequence to expose as an event. + /// Delegate used to invoke the event for each element of the sequence. + /// or is null. + protected EventPatternSourceBase(IObservable> source, Action, /*object,*/ EventPattern> invokeHandler) + { + _source = source ?? throw new ArgumentNullException(nameof(source)); + _invokeHandler = invokeHandler ?? throw new ArgumentNullException(nameof(invokeHandler)); + _subscriptions = new Dictionary>(); + } + + /// + /// Adds the specified event handler, causing a subscription to the underlying source. + /// + /// Event handler to add. The same delegate should be passed to the operation in order to remove the event handler. + /// Invocation delegate to raise the event in the derived class. + /// or is null. + protected void Add(Delegate handler, Action invoke) + { + if (handler == null) + { + throw new ArgumentNullException(nameof(handler)); + } + + if (invoke == null) + { + throw new ArgumentNullException(nameof(invoke)); + } + + var observer = new Observer(this, handler, invoke); + // + // [OK] Use of unsafe Subscribe: non-pretentious wrapper of an observable in an event; exceptions can occur during +=. + // + observer.SetResource(_source.Subscribe(observer)); + } + + private void Add(Delegate handler, IDisposable disposable) + { + lock (_subscriptions) + { + if (!_subscriptions.TryGetValue(handler, out var l)) + { + _subscriptions[handler] = l = new Stack(); + } + + l.Push(disposable); + } + } + + /// + /// Removes the specified event handler, causing a disposal of the corresponding subscription to the underlying source that was created during the operation. + /// + /// Event handler to remove. This should be the same delegate as one that was passed to the operation. + /// is null. + protected void Remove(Delegate handler) + { + if (handler == null) + { + throw new ArgumentNullException(nameof(handler)); + } + + IDisposable? d = null; + + lock (_subscriptions) + { + if (_subscriptions.TryGetValue(handler, out var l)) + { + d = l.Pop(); + + if (l.Count == 0) + { + _subscriptions.Remove(handler); + } + } + } + + d?.Dispose(); + } + } +} diff --git a/LibExternal/System.Reactive/EventSource.cs b/LibExternal/System.Reactive/EventSource.cs new file mode 100644 index 0000000..85dc2cf --- /dev/null +++ b/LibExternal/System.Reactive/EventSource.cs @@ -0,0 +1,105 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive +{ + internal sealed class EventSource : IEventSource + { + private readonly IObservable _source; + private readonly Dictionary> _subscriptions; + private readonly Action, /*object,*/ T> _invokeHandler; + + public EventSource(IObservable source, Action, /*object,*/ T> invokeHandler) + { + _source = source; + _invokeHandler = invokeHandler; + _subscriptions = new Dictionary>(); + } + + public event Action OnNext + { + add + { + var gate = new object(); + var isAdded = false; + var isDone = false; + + var remove = new Action(() => + { + lock (gate) + { + if (isAdded) + { + Remove(value); + } + else + { + isDone = true; + } + } + }); + + // + // [OK] Use of unsafe Subscribe: non-pretentious wrapper of an observable in an event; exceptions can occur during +=. + // + var d = _source.Subscribe/*Unsafe*/( + x => _invokeHandler(value, /*this,*/ x), + ex => { remove(); ex.Throw(); }, + remove + ); + + lock (gate) + { + if (!isDone) + { + Add(value, d); + isAdded = true; + } + } + } + + remove + { + Remove(value); + } + } + + private void Add(Delegate handler, IDisposable disposable) + { + lock (_subscriptions) + { + if (!_subscriptions.TryGetValue(handler, out var l)) + { + _subscriptions[handler] = l = new Stack(); + } + + l.Push(disposable); + } + } + + private void Remove(Delegate handler) + { + IDisposable? d = null; + + lock (_subscriptions) + { + var l = new Stack(); + + if (_subscriptions.TryGetValue(handler, out l)) + { + d = l.Pop(); + + if (l.Count == 0) + { + _subscriptions.Remove(handler); + } + } + } + + d?.Dispose(); + } + } +} diff --git a/LibExternal/System.Reactive/ExperimentalAttribute.cs b/LibExternal/System.Reactive/ExperimentalAttribute.cs new file mode 100644 index 0000000..a7688ee --- /dev/null +++ b/LibExternal/System.Reactive/ExperimentalAttribute.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive +{ + /// + /// Marks the program elements that are experimental. This class cannot be inherited. + /// + [Experimental, AttributeUsage(AttributeTargets.All)] + public sealed class ExperimentalAttribute : Attribute + { + } +} diff --git a/LibExternal/System.Reactive/GlobalSuppressions.cs b/LibExternal/System.Reactive/GlobalSuppressions.cs new file mode 100644 index 0000000..9874007 --- /dev/null +++ b/LibExternal/System.Reactive/GlobalSuppressions.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. +// +// To add a suppression to this file, right-click the message in the +// Error List, point to "Suppress Message(s)", and click +// "In Project Suppression File". +// You do not need to add suppressions to this file manually. + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "~N:System", Justification = "By design.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "Taken care of by lab build.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1016:MarkAssembliesWithAssemblyVersion", Justification = "Taken care of by lab build.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Scope = "member", Target = "~M:System.Reactive.Concurrency.LocalScheduler.WorkItem.CompareTo(System.Reactive.Concurrency.LocalScheduler.WorkItem)~System.Int32", Justification = "Checked all enqueue operations against null reference insertions.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "~N:System.Reactive", Justification = "By design.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "~N:System.Reactive.Disposables", Justification = "By design.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "~N:System.Reactive.Linq", Justification = "By design.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "~N:System.Reactive.Subjects", Justification = "By design.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "~N:System.Reactive.Concurrency", Justification = "By design.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "~N:System.Reactive.Linq", Justification = "By design.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "~N:System.Reactive.Threading.Tasks", Justification = "By design.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "Taken care of by lab build.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1016:MarkAssembliesWithAssemblyVersion", Justification = "Taken care of by lab build.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Scope = "member", Target = "~M:System.Reactive.Linq.ObservableImpl.Dematerialize`1._.OnNext(System.Reactive.Notification{`0})", Justification = "Producer cannot pass null to setSink parameter.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Scope = "member", Target = "~M:System.Reactive.Either`2.Left.Switch(System.Action{`0},System.Action{`1})", Justification = "Producer cannot pass null to setSink parameter.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Scope = "member", Target = "~M:System.Reactive.Either`2.Left.Switch``1(System.Func{`0,``0},System.Func{`1,``0})~``0", Justification = "Producer cannot pass null to setSink parameter.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Scope = "member", Target = "~M:System.Reactive.Either`2.Right.Switch(System.Action{`0},System.Action{`1})", Justification = "Producer cannot pass null to setSink parameter.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Scope = "member", Target = "~M:System.Reactive.Either`2.Right.Switch``1(System.Func{`0,``0},System.Func{`1,``0})~``0", Justification = "Producer cannot pass null to setSink parameter.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Scope = "member", Target = "~M:System.Reactive.Joins.JoinObserver`1.OnNextCore(System.Reactive.Notification{`0})", Justification = "Producer cannot pass null to setSink parameter.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Scope = "member", Target = "~M:System.Reactive.Linq.ObservableImpl.ElementAt`1._.OnCompleted", Justification = "Asynchronous behavior; no more index parameter in scope.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "~N:System.Reactive.Concurrency", Justification = "By design.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "~N:System.Reactive.PlatformServices", Justification = "By design.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "Taken care of by lab build.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1016:MarkAssembliesWithAssemblyVersion", Justification = "Taken care of by lab build.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "~N:System.Reactive.Linq", Justification = "By design")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Qbservable", Scope = "type", Target = "~T:System.Reactive.Linq.IQbservable", Justification = "It's homoiconic, dude!")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Qbservable", Scope = "type", Target = "~T:System.Reactive.Linq.IQbservable`1", Justification = "It's homoiconic, dude!")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Qbservable", Scope = "type", Target = "~T:System.Reactive.Linq.IQbservableProvider", Justification = "It's homoiconic, dude!")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Qbservable", Scope = "type", Target = "~T:System.Reactive.Linq.Qbservable", Justification = "It's homoiconic, dude!")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Amb", Scope = "member", Target = "~M:System.Reactive.Linq.Qbservable.Amb``1(System.Reactive.Linq.IQbservable{``0},System.IObservable{``0})~System.Reactive.Linq.IQbservable{``0}", Justification = "It's homoiconic, dude!")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Amb", Scope = "member", Target = "~M:System.Reactive.Linq.Qbservable.Amb``1(System.Reactive.Linq.IQbservableProvider,System.Collections.Generic.IEnumerable{System.IObservable{``0}})~System.Reactive.Linq.IQbservable{``0}", Justification = "It's homoiconic, dude!")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Amb", Scope = "member", Target = "~M:System.Reactive.Linq.Qbservable.Amb``1(System.Reactive.Linq.IQbservableProvider,System.IObservable{``0}[])~System.Reactive.Linq.IQbservable{``0}", Justification = "It's homoiconic, dude!")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Qbservable", Scope = "member", Target = "~M:System.Reactive.Linq.Qbservable.AsQbservable``1(System.IObservable{``0})~System.Reactive.Linq.IQbservable{``0}", Justification = "It's homoiconic, dude!")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Qbservable", Scope = "member", Target = "~M:System.Reactive.Linq.Qbservable.ToQbservable``1(System.Linq.IQueryable{``0})~System.Reactive.Linq.IQbservable{``0}", Justification = "It's homoiconic, dude!")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "Qbservable", Scope = "resource", Target = "System.Reactive.Strings_Providers.resources", Justification = "It's homoiconic, dude!")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Qbservable", Scope = "member", Target = "~M:System.Reactive.Linq.Qbservable.ToQbservable``1(System.Linq.IQueryable{``0},System.Reactive.Concurrency.IScheduler)~System.Reactive.Linq.IQbservable{``0}", Justification = "It's homoiconic, dude!")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Scope = "member", Target = "~M:System.Reactive.ObservableQuery`1.ObservableRewriter.VisitConstant(System.Linq.Expressions.ConstantExpression)~System.Linq.Expressions.Expression", Justification = "Expression visitor should not pass in null references.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Scope = "member", Target = "~M:System.Reactive.ObservableQuery`1.ObservableRewriter.VisitMethodCall(System.Linq.Expressions.MethodCallExpression)~System.Linq.Expressions.Expression", Justification = "Expression visitor should not pass in null references.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "Taken care of by lab build.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1016:MarkAssembliesWithAssemblyVersion", Justification = "Taken care of by lab build.")] diff --git a/LibExternal/System.Reactive/IEventPattern.cs b/LibExternal/System.Reactive/IEventPattern.cs new file mode 100644 index 0000000..c96938c --- /dev/null +++ b/LibExternal/System.Reactive/IEventPattern.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive +{ + /// + /// Represents a .NET event invocation consisting of the strongly typed object that raised the event and the data that was generated by the event. + /// + /// + /// The type of the sender that raised the event. + /// This type parameter is covariant. That is, you can use either the type you specified or any type that is more derived. For more information about covariance and contravariance, see Covariance and Contravariance in Generics. + /// + /// + /// The type of the event data generated by the event. + /// This type parameter is covariant. That is, you can use either the type you specified or any type that is more derived. For more information about covariance and contravariance, see Covariance and Contravariance in Generics. + /// + public interface IEventPattern + { + /// + /// Gets the sender object that raised the event. + /// + TSender? Sender { get; } + + /// + /// Gets the event data that was generated by the event. + /// + TEventArgs EventArgs { get; } + } +} diff --git a/LibExternal/System.Reactive/IEventPatternSource.cs b/LibExternal/System.Reactive/IEventPatternSource.cs new file mode 100644 index 0000000..eae6d6b --- /dev/null +++ b/LibExternal/System.Reactive/IEventPatternSource.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive +{ + /// + /// Represents a data stream signaling its elements by means of an event. + /// + /// The type of the event data generated by the event. + public interface IEventPatternSource + { + /// + /// Event signaling the next element in the data stream. + /// + event EventHandler OnNext; + } +} diff --git a/LibExternal/System.Reactive/IEventSource.cs b/LibExternal/System.Reactive/IEventSource.cs new file mode 100644 index 0000000..ec8920c --- /dev/null +++ b/LibExternal/System.Reactive/IEventSource.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive +{ + /// + /// Represents a data stream signaling its elements by means of an event. + /// + /// + /// The type of the event data generated by the event. + /// This type parameter is covariant. That is, you can use either the type you specified or any type that is more derived. For more information about covariance and contravariance, see Covariance and Contravariance in Generics. + /// + public interface IEventSource + { + /// + /// Event signaling the next element in the data stream. + /// + [Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1003:Use generic event handler instances", Justification = "Can't do this for Action.")] + event Action OnNext; + } +} diff --git a/LibExternal/System.Reactive/IObserver.Result.cs b/LibExternal/System.Reactive/IObserver.Result.cs new file mode 100644 index 0000000..86cfada --- /dev/null +++ b/LibExternal/System.Reactive/IObserver.Result.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive +{ + /// + /// Provides a mechanism for receiving push-based notifications and returning a response. + /// + /// + /// The type of the elements received by the observer. + /// This type parameter is contravariant. That is, you can use either the type you specified or any type that is less derived. For more information about covariance and contravariance, see Covariance and Contravariance in Generics. + /// + /// + /// The type of the result returned from the observer's notification handlers. + /// This type parameter is covariant. That is, you can use either the type you specified or any type that is more derived. For more information about covariance and contravariance, see Covariance and Contravariance in Generics. + /// + public interface IObserver + { + /// + /// Notifies the observer of a new element in the sequence. + /// + /// The new element in the sequence. + /// Result returned upon observation of a new element. + TResult OnNext(TValue value); + + /// + /// Notifies the observer that an exception has occurred. + /// + /// The exception that occurred. + /// Result returned upon observation of an error. + TResult OnError(Exception exception); + + /// + /// Notifies the observer of the end of the sequence. + /// + /// Result returned upon observation of the sequence completion. + TResult OnCompleted(); + } +} diff --git a/LibExternal/System.Reactive/Internal/AnonymousEnumerable.cs b/LibExternal/System.Reactive/Internal/AnonymousEnumerable.cs new file mode 100644 index 0000000..5fc1891 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/AnonymousEnumerable.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Collections.Generic; + +namespace System.Reactive +{ + internal sealed class AnonymousEnumerable : IEnumerable + { + private readonly Func> _getEnumerator; + + public AnonymousEnumerable(Func> getEnumerator) + { + _getEnumerator = getEnumerator; + } + + public IEnumerator GetEnumerator() => _getEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => _getEnumerator(); + } +} diff --git a/LibExternal/System.Reactive/Internal/AsyncLockObserver.cs b/LibExternal/System.Reactive/Internal/AsyncLockObserver.cs new file mode 100644 index 0000000..a414d25 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/AsyncLockObserver.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; + +namespace System.Reactive +{ + internal sealed class AsyncLockObserver : ObserverBase + { + private readonly AsyncLock _gate; + private readonly IObserver _observer; + + public AsyncLockObserver(IObserver observer, AsyncLock gate) + { + _gate = gate; + _observer = observer; + } + + protected override void OnNextCore(T value) + { + _gate.Wait( + (_observer, value), + static tuple => tuple._observer.OnNext(tuple.value)); + } + + protected override void OnErrorCore(Exception exception) + { + _gate.Wait( + (_observer, exception), + static tuple => tuple._observer.OnError(tuple.exception)); + } + + protected override void OnCompletedCore() + { + _gate.Wait( + _observer, + static closureObserver => closureObserver.OnCompleted()); + } + } +} diff --git a/LibExternal/System.Reactive/Internal/AutoDetachObserver.cs b/LibExternal/System.Reactive/Internal/AutoDetachObserver.cs new file mode 100644 index 0000000..8d96ca4 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/AutoDetachObserver.cs @@ -0,0 +1,107 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; + +namespace System.Reactive +{ + internal sealed class AutoDetachObserver : ObserverBase, ISafeObserver + { + private readonly IObserver _observer; + + private SingleAssignmentDisposableValue _disposable; + + public AutoDetachObserver(IObserver observer) + { + _observer = observer; + } + + public void SetResource(IDisposable resource) + { + _disposable.Disposable = resource; + } + + protected override void OnNextCore(T value) + { + // + // Safeguarding of the pipeline against rogue observers is required for proper + // resource cleanup. Consider the following example: + // + // var xs = Observable.Interval(TimeSpan.FromSeconds(1)); + // var ys = ; + // var res = xs.CombineLatest(ys, (x, y) => x + y); + // + // The marble diagram of the query above looks as follows: + // + // xs -----0-----1-----2-----3-----4-----5-----6-----7-----8-----9---... + // | | | | | | | | | + // ys --------4--+--5--+-----+--2--+--1--+-----+-----+--0--+-----+---... + // | | | | | | | | | | | | | | + // v v v v v v v v v v v v v v + // res --------4--5--6--7-----8--5--6--5--6-----7-----8--7--8-----9---... + // | + // @#& + // + // Notice the free-threaded nature of Rx, where messages on the resulting sequence + // are produced by either of the two input sequences to CombineLatest. + // + // Now assume an exception happens in the OnNext callback for the observer of res, + // at the indicated point marked with @#& above. The callback runs in the context + // of ys, so the exception will take down the scheduler thread of ys. This by + // itself is a problem (that can be mitigated by a Catch operator on IScheduler), + // but notice how the timer that produces xs is kept alive. + // + // The safe-guarding code below ensures the acquired resources are disposed when + // the user callback throws. + // + var __noError = false; + try + { + _observer.OnNext(value); + __noError = true; + } + finally + { + if (!__noError) + { + Dispose(); + } + } + } + + protected override void OnErrorCore(Exception exception) + { + try + { + _observer.OnError(exception); + } + finally + { + Dispose(); + } + } + + protected override void OnCompletedCore() + { + try + { + _observer.OnCompleted(); + } + finally + { + Dispose(); + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _disposable.Dispose(); + } + } + } +} diff --git a/LibExternal/System.Reactive/Internal/BinaryObserver.cs b/LibExternal/System.Reactive/Internal/BinaryObserver.cs new file mode 100644 index 0000000..1f052dd --- /dev/null +++ b/LibExternal/System.Reactive/Internal/BinaryObserver.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive +{ + internal sealed class BinaryObserver : IObserver, Notification>> + { + public BinaryObserver(IObserver leftObserver, IObserver rightObserver) + { + LeftObserver = leftObserver; + RightObserver = rightObserver; + } + + public BinaryObserver(Action> left, Action> right) + : this(left.ToObserver(), right.ToObserver()) + { + } + + public IObserver LeftObserver { get; } + public IObserver RightObserver { get; } + + void IObserver, Notification>>.OnNext(Either, Notification> value) + { + value.Switch(left => left.Accept(LeftObserver), right => right.Accept(RightObserver)); + } + + void IObserver, Notification>>.OnError(Exception exception) + { + } + + void IObserver, Notification>>.OnCompleted() + { + } + } +} diff --git a/LibExternal/System.Reactive/Internal/CheckedObserver.cs b/LibExternal/System.Reactive/Internal/CheckedObserver.cs new file mode 100644 index 0000000..94d5785 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/CheckedObserver.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Threading; + +namespace System.Reactive +{ + internal sealed class CheckedObserver : IObserver + { + private readonly IObserver _observer; + private int _state; + + private const int Idle = 0; + private const int Busy = 1; + private const int Done = 2; + + public CheckedObserver(IObserver observer) + { + _observer = observer; + } + + public void OnNext(T value) + { + CheckAccess(); + + try + { + _observer.OnNext(value); + } + finally + { + Interlocked.Exchange(ref _state, Idle); + } + } + + public void OnError(Exception error) + { + CheckAccess(); + + try + { + _observer.OnError(error); + } + finally + { + Interlocked.Exchange(ref _state, Done); + } + } + + public void OnCompleted() + { + CheckAccess(); + + try + { + _observer.OnCompleted(); + } + finally + { + Interlocked.Exchange(ref _state, Done); + } + } + + private void CheckAccess() + { + switch (Interlocked.CompareExchange(ref _state, Busy, Idle)) + { + case Busy: + throw new InvalidOperationException(Strings_Core.REENTRANCY_DETECTED); + case Done: + throw new InvalidOperationException(Strings_Core.OBSERVER_TERMINATED); + } + } + } +} diff --git a/LibExternal/System.Reactive/Internal/ConcatSink.cs b/LibExternal/System.Reactive/Internal/ConcatSink.cs new file mode 100644 index 0000000..62319d1 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/ConcatSink.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive +{ + internal abstract class ConcatSink : TailRecursiveSink + { + protected ConcatSink(IObserver observer) + : base(observer) + { + } + + protected override IEnumerable>? Extract(IObservable source) => (source as IConcatenatable)?.GetSources(); + + public override void OnCompleted() => Recurse(); + } +} diff --git a/LibExternal/System.Reactive/Internal/Constants.cs b/LibExternal/System.Reactive/Internal/Constants.cs new file mode 100644 index 0000000..4dc8403 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/Constants.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive +{ + // We can't make those based on the Strings_Core.resx file, because attributes need a compile-time constant. + + internal static class Constants_Core + { + private const string ObsoleteRefactoring = "This property is no longer supported due to refactoring of the API surface and elimination of platform-specific dependencies."; + + public const string ObsoleteSchedulerNewthread = ObsoleteRefactoring + " Please use NewThreadScheduler.Default to obtain an instance of this scheduler type."; + public const string ObsoleteSchedulerTaskpool = ObsoleteRefactoring + " Please use TaskPoolScheduler.Default to obtain an instance of this scheduler type."; + public const string ObsoleteSchedulerThreadpool = ObsoleteRefactoring + " Consider using Scheduler.Default to obtain the platform's most appropriate pool-based scheduler. In order to access a specific pool-based scheduler, please add a reference to the System.Reactive.PlatformServices assembly for your target platform and use the appropriate scheduler in the System.Reactive.Concurrency namespace."; + + public const string ObsoleteSchedulerequired = "This instance property is no longer supported. Use CurrentThreadScheduler.IsScheduleRequired instead."; + + internal const string AsQueryableTrimIncompatibilityMessage = "This type uses Queryable.AsQueryable, which is not compatible with trimming because expressions referencing IQueryable extension methods can get rebound to IEnumerable extension methods, and those IEnumerable methods might be trimmed."; + internal const string EventReflectionTrimIncompatibilityMessage = "This member uses reflection to discover event members and associated delegate types."; + } + + // We can't make those based on the Strings_*.resx file, because the ObsoleteAttribute needs a compile-time constant. + + internal static class Constants_Linq + { + public const string UseAsync = "This blocking operation is no longer supported. Instead, use the async version in combination with C# and Visual Basic async/await support. In case you need a blocking operation, use Wait or convert the resulting observable sequence to a Task object and block."; + public const string UseTaskFromAsyncPattern = "This conversion is no longer supported. Replace use of the Begin/End asynchronous method pair with a new Task-based async method, and convert the result using ToObservable. If no Task-based async method is available, use Task.Factory.FromAsync to obtain a Task object."; + } +} diff --git a/LibExternal/System.Reactive/Internal/CurrentPlatformEnlightenmentProvider.cs b/LibExternal/System.Reactive/Internal/CurrentPlatformEnlightenmentProvider.cs new file mode 100644 index 0000000..5707977 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/CurrentPlatformEnlightenmentProvider.cs @@ -0,0 +1,80 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +// +// WARNING: The full namespace-qualified type name should stay the same for the discovery in System.Reactive.Core to work! +// +using System.ComponentModel; +using System.Diagnostics; +using System.Reactive.Concurrency; +using System.Reactive.Linq; +using System.Reflection; + +namespace System.Reactive.PlatformServices +{ + /// + /// (Infrastructure) Provider for platform-specific framework enlightenments. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public class CurrentPlatformEnlightenmentProvider : IPlatformEnlightenmentProvider + { + /// + /// (Infrastructure) Tries to gets the specified service. + /// + /// Service type. + /// Optional set of arguments. + /// Service instance or null if not found. + public virtual T? GetService(object[] args) where T : class + { + var t = typeof(T); + + if (t == typeof(IExceptionServices)) + { + return (T)(object)new ExceptionServicesImpl(); + } + + if (t == typeof(IConcurrencyAbstractionLayer)) + { + return (T)(object)new ConcurrencyAbstractionLayerImpl(); + } + + if (t == typeof(IScheduler) && args != null) + { + switch ((string)args[0]) + { + case "ThreadPool": + return (T)(object)ThreadPoolScheduler.Instance; + case "TaskPool": + return (T)(object)TaskPoolScheduler.Default; + case "NewThread": + return (T)(object)NewThreadScheduler.Default; + } + } + +#if WINDOWS + if (t == typeof(IHostLifecycleNotifications)) + { + return (T)(object)new HostLifecycleNotifications(); + } +#endif + + if (t == typeof(IQueryServices)) + { + // + // We perform this Debugger.IsAttached check early rather than deferring + // the decision to intercept query operator methods to the debugger + // assembly that's dynamically discovered here. Also, it's a reasonable + // expectation it'd be pretty hard to turn on interception dynamically + // upon a debugger attach event, so we should make this check early. + // + if (Debugger.IsAttached) + { + return (T)(object)new QueryDebugger(); + } + } + + return null; + } + } +} diff --git a/LibExternal/System.Reactive/Internal/Either.Generic.cs b/LibExternal/System.Reactive/Internal/Either.Generic.cs new file mode 100644 index 0000000..c2b40e2 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/Either.Generic.cs @@ -0,0 +1,96 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Globalization; + +namespace System.Reactive +{ + internal abstract class Either + { + private Either() + { + } + + public static Either CreateLeft(TLeft value) => new Left(value); + + public static Either CreateRight(TRight value) => new Right(value); + + public abstract TResult Switch(Func caseLeft, Func caseRight); + public abstract void Switch(Action caseLeft, Action caseRight); + + public sealed class Left : Either, IEquatable + { + public Left(TLeft value) + { + Value = value; + } + + public TLeft Value { get; } + + public override TResult Switch(Func caseLeft, Func caseRight) => caseLeft(Value); + + public override void Switch(Action caseLeft, Action caseRight) => caseLeft(Value); + + public bool Equals(Left? other) + { + if (other == this) + { + return true; + } + + if (other == null) + { + return false; + } + + return EqualityComparer.Default.Equals(Value, other.Value); + } + + public override bool Equals(object? obj) => Equals(obj as Left); + + public override int GetHashCode() => Value?.GetHashCode() ?? 0; + + public override string ToString() + { + return string.Format(CultureInfo.CurrentCulture, "Left({0})", Value); + } + } + + public sealed class Right : Either, IEquatable + { + public Right(TRight value) + { + Value = value; + } + + public TRight Value { get; } + + public override TResult Switch(Func caseLeft, Func caseRight) => caseRight(Value); + + public override void Switch(Action caseLeft, Action caseRight) => caseRight(Value); + + public bool Equals(Right? other) + { + if (other == this) + { + return true; + } + + if (other == null) + { + return false; + } + + return EqualityComparer.Default.Equals(Value, other.Value); + } + + public override bool Equals(object? obj) => Equals(obj as Right); + + public override int GetHashCode() => Value?.GetHashCode() ?? 0; + + public override string ToString() => string.Format(CultureInfo.CurrentCulture, "Right({0})", Value); + } + } +} diff --git a/LibExternal/System.Reactive/Internal/ExceptionHelper.cs b/LibExternal/System.Reactive/Internal/ExceptionHelper.cs new file mode 100644 index 0000000..1c15fc3 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/ExceptionHelper.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.IO; +using System.Threading; + +namespace System.Reactive +{ + /// + /// Utility methods to handle lock-free combining of Exceptions + /// as well as hosting a terminal-exception indicator for + /// lock-free termination support. + /// + internal static class ExceptionHelper + { + /// + /// The singleton instance of the exception indicating a terminal state, + /// DO NOT LEAK or signal this via OnError! + /// + public static Exception Terminated { get; } = new EndOfStreamException("On[Error|Completed]"); + + /// + /// Tries to atomically set the Exception on the given field if it is + /// still null. + /// + /// The target field to try to set atomically. + /// The exception to set, not null (not verified). + /// True if the operation succeeded, false if the target was not null. + public static bool TrySetException(ref Exception? field, Exception ex) + { + return Interlocked.CompareExchange(ref field, ex, null) == null; + } + } +} diff --git a/LibExternal/System.Reactive/Internal/ExceptionServices.Default.cs b/LibExternal/System.Reactive/Internal/ExceptionServices.Default.cs new file mode 100644 index 0000000..838cad2 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/ExceptionServices.Default.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.ExceptionServices; + +namespace System.Reactive.PlatformServices +{ + // + // WARNING: This code is kept *identically* in two places. One copy is kept in System.Reactive.Core for non-PLIB platforms. + // Another copy is kept in System.Reactive.PlatformServices to enlighten the default lowest common denominator + // behavior of Rx for PLIB when used on a more capable platform. + // + internal sealed class DefaultExceptionServices/*Impl*/ : IExceptionServices + { +#if NO_NULLABLE_ATTRIBUTES +#pragma warning disable CS8763 // NB: On down-level platforms, Throw is not marked as DoesNotReturn. +#endif + [DoesNotReturn] + public void Rethrow(Exception exception) => ExceptionDispatchInfo.Capture(exception).Throw(); +#if NO_NULLABLE_ATTRIBUTES +#pragma warning restore CS8763 +#endif + } +} diff --git a/LibExternal/System.Reactive/Internal/ExceptionServices.cs b/LibExternal/System.Reactive/Internal/ExceptionServices.cs new file mode 100644 index 0000000..bb3f848 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/ExceptionServices.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Reactive.PlatformServices; + +namespace System.Reactive +{ + internal static class ExceptionHelpers + { + private static readonly Lazy Services = new(Initialize); + + [DoesNotReturn] + public static void Throw(this Exception exception) => Services.Value.Rethrow(exception); + + private static IExceptionServices Initialize() + { + return PlatformEnlightenmentProvider.Current.GetService() ?? new DefaultExceptionServices(); + } + } +} + +namespace System.Reactive.PlatformServices +{ + /// + /// (Infrastructure) Services to rethrow exceptions. + /// + /// + /// This type is used by the Rx infrastructure and not meant for public consumption or implementation. + /// No guarantees are made about forward compatibility of the type's functionality and its usage. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public interface IExceptionServices + { + /// + /// Rethrows the specified exception. + /// + /// Exception to rethrow. + [DoesNotReturn] + void Rethrow(Exception exception); + } +} diff --git a/LibExternal/System.Reactive/Internal/ExceptionServicesImpl.cs b/LibExternal/System.Reactive/Internal/ExceptionServicesImpl.cs new file mode 100644 index 0000000..304f106 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/ExceptionServicesImpl.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.ExceptionServices; + +namespace System.Reactive.PlatformServices +{ + // + // WARNING: This code is kept *identically* in two places. One copy is kept in System.Reactive.Core for non-PLIB platforms. + // Another copy is kept in System.Reactive.PlatformServices to enlighten the default lowest common denominator + // behavior of Rx for PLIB when used on a more capable platform. + // + internal sealed class /*Default*/ExceptionServicesImpl : IExceptionServices + { +#if NO_NULLABLE_ATTRIBUTES +#pragma warning disable CS8763 // NB: On down-level platforms, Throw is not marked as DoesNotReturn. +#endif + [DoesNotReturn] + public void Rethrow(Exception exception) => ExceptionDispatchInfo.Capture(exception).Throw(); +#if NO_NULLABLE_ATTRIBUTES +#pragma warning restore CS8763 +#endif + } +} diff --git a/LibExternal/System.Reactive/Internal/Grouping.cs b/LibExternal/System.Reactive/Internal/Grouping.cs new file mode 100644 index 0000000..3521ee2 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/Grouping.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +#nullable disable + +using System.Collections.Generic; +using System.Reactive.Subjects; + +namespace System.Reactive +{ + internal sealed class Grouping : Dictionary> + { + public Grouping(IEqualityComparer comparer) + : base(comparer) + { + } + + public Grouping(int capacity, IEqualityComparer comparer) + : base(capacity, comparer) + { + } + } +} diff --git a/LibExternal/System.Reactive/Internal/HalfSerializer.cs b/LibExternal/System.Reactive/Internal/HalfSerializer.cs new file mode 100644 index 0000000..b182878 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/HalfSerializer.cs @@ -0,0 +1,103 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Threading; + +namespace System.Reactive +{ + /// + /// Utility methods for dealing with serializing OnXXX signals + /// for an IObserver where concurrent OnNext is still not allowed + /// but concurrent OnError/OnCompleted may happen. + /// This serialization case is generally lower overhead than + /// a full SerializedObserver wrapper and doesn't need + /// allocation. + /// + internal static class HalfSerializer + { + /// + /// Signals the given item to the observer in a serialized fashion + /// allowing a concurrent OnError or OnCompleted emission to be delayed until + /// the observer.OnNext returns. + /// Do not call OnNext from multiple threads as it may lead to ignored items. + /// Use a full SerializedObserver wrapper for merging multiple sequences. + /// + /// The element type of the observer. + /// The observer to signal events in a serialized fashion. + /// The item to signal. + /// Indicates there is an emission going on currently. + /// The field containing an error or terminal indicator. + public static void ForwardOnNext(ISink sink, T item, ref int wip, ref Exception? error) + { + if (Interlocked.CompareExchange(ref wip, 1, 0) == 0) + { + sink.ForwardOnNext(item); + + if (Interlocked.Decrement(ref wip) != 0) + { + var ex = error!; // NB: A concurrent OnError or OnCompleted will either set Terminated or the original exception, so never null here. + if (ex != ExceptionHelper.Terminated) + { + error = ExceptionHelper.Terminated; + sink.ForwardOnError(ex); + } + else + { + sink.ForwardOnCompleted(); + } + } + } + else if (error == null) + Trace.TraceWarning("OnNext called while another OnNext call was in progress on the same Observer."); + } + + /// + /// Signals the given exception to the observer. If there is a concurrent + /// OnNext emission is happening, saves the exception into the given field + /// otherwise to be picked up by . + /// This method can be called concurrently with itself and the other methods of this + /// helper class but only one terminal signal may actually win. + /// + /// The element type of the observer. + /// The observer to signal events in a serialized fashion. + /// The exception to signal sooner or later. + /// Indicates there is an emission going on currently. + /// The field containing an error or terminal indicator. + public static void ForwardOnError(ISink sink, Exception ex, ref int wip, ref Exception? error) + { + if (ExceptionHelper.TrySetException(ref error, ex)) + { + if (Interlocked.Increment(ref wip) == 1) + { + error = ExceptionHelper.Terminated; + sink.ForwardOnError(ex); + } + } + } + + /// + /// Signals OnCompleted on the observer. If there is a concurrent + /// OnNext emission happening, the error field will host a special + /// terminal exception signal to be picked up by once it finishes with OnNext and signal the + /// OnCompleted as well. + /// This method can be called concurrently with itself and the other methods of this + /// helper class but only one terminal signal may actually win. + /// + /// The element type of the observer. + /// The observer to signal events in a serialized fashion. + /// Indicates there is an emission going on currently. + /// The field containing an error or terminal indicator. + public static void ForwardOnCompleted(ISink sink, ref int wip, ref Exception? error) + { + if (ExceptionHelper.TrySetException(ref error, ExceptionHelper.Terminated)) + { + if (Interlocked.Increment(ref wip) == 1) + { + sink.ForwardOnCompleted(); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Internal/Helpers.cs b/LibExternal/System.Reactive/Internal/Helpers.cs new file mode 100644 index 0000000..4c54f21 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/Helpers.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive +{ + internal static class Helpers + { + public static bool All(this bool[] values) + { + foreach (var value in values) + { + if (!value) + { + return false; + } + } + + return true; + } + + public static bool AllExcept(this bool[] values, int index) + { + for (var i = 0; i < values.Length; i++) + { + if (i != index) + { + if (!values[i]) + { + return false; + } + } + } + + return true; + } + } +} diff --git a/LibExternal/System.Reactive/Internal/HostLifecycleNotifications.Windows.cs b/LibExternal/System.Reactive/Internal/HostLifecycleNotifications.Windows.cs new file mode 100644 index 0000000..0b55bc0 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/HostLifecycleNotifications.Windows.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +#if WINDOWS +using Windows.ApplicationModel; +using Windows.ApplicationModel.Core; + +namespace System.Reactive.PlatformServices +{ + internal sealed class HostLifecycleNotifications : IHostLifecycleNotifications + { + private EventHandler? _suspending; + private EventHandler? _resuming; + + public event EventHandler Suspending + { + add + { + _suspending = (o, e) => value(o, new HostSuspendingEventArgs()); + CoreApplication.Suspending += _suspending; + } + + remove + { + CoreApplication.Suspending -= _suspending; + } + } + + public event EventHandler Resuming + { + add + { + _resuming = (o, e) => value(o, new HostResumingEventArgs()); + CoreApplication.Resuming += _resuming; + } + + remove + { + CoreApplication.Resuming -= _resuming; + } + } + } +} +#endif diff --git a/LibExternal/System.Reactive/Internal/HostLifecycleService.cs b/LibExternal/System.Reactive/Internal/HostLifecycleService.cs new file mode 100644 index 0000000..bd30233 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/HostLifecycleService.cs @@ -0,0 +1,111 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Threading; + +namespace System.Reactive.PlatformServices +{ + /// + /// (Infrastructure) Provides access to the host's lifecycle management services. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class HostLifecycleService + { + private static readonly Lazy Notifications = new(InitializeNotifications); + + private static int _refCount; + + /// + /// Event that gets raised when the host suspends the application. + /// + public static event EventHandler? Suspending; + + /// + /// Event that gets raised when the host resumes the application. + /// + public static event EventHandler? Resuming; + + /// + /// Adds a reference to the host lifecycle manager, causing it to be sending notifications. + /// + public static void AddRef() + { + if (Interlocked.Increment(ref _refCount) == 1) + { + var notifications = Notifications.Value; + if (notifications != null) + { + notifications.Suspending += OnSuspending; + notifications.Resuming += OnResuming; + } + } + } + + /// + /// Removes a reference to the host lifecycle manager, causing it to stop sending notifications + /// if the removed reference was the last one. + /// + public static void Release() + { + if (Interlocked.Decrement(ref _refCount) == 0) + { + var notifications = Notifications.Value; + if (notifications != null) + { + notifications.Suspending -= OnSuspending; + notifications.Resuming -= OnResuming; + } + } + } + + private static void OnSuspending(object? sender, HostSuspendingEventArgs e) + { + Suspending?.Invoke(sender, e); + } + + private static void OnResuming(object? sender, HostResumingEventArgs e) + { + Resuming?.Invoke(sender, e); + } + + private static IHostLifecycleNotifications? InitializeNotifications() + { + return PlatformEnlightenmentProvider.Current.GetService(); + } + } + + /// + /// (Infrastructure) Provides notifications about the host's lifecycle events. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public interface IHostLifecycleNotifications + { + /// + /// Event that gets raised when the host suspends. + /// + event EventHandler Suspending; + + /// + /// Event that gets raised when the host resumes. + /// + event EventHandler Resuming; + } + + /// + /// (Infrastructure) Event arguments for host suspension events. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public class HostSuspendingEventArgs : EventArgs + { + } + + /// + /// (Infrastructure) Event arguments for host resumption events. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public class HostResumingEventArgs : EventArgs + { + } +} diff --git a/LibExternal/System.Reactive/Internal/IConcatenatable.cs b/LibExternal/System.Reactive/Internal/IConcatenatable.cs new file mode 100644 index 0000000..665a359 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/IConcatenatable.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive +{ + internal interface IConcatenatable + { + IEnumerable> GetSources(); + } +} diff --git a/LibExternal/System.Reactive/Internal/IEvaluatableObservable.cs b/LibExternal/System.Reactive/Internal/IEvaluatableObservable.cs new file mode 100644 index 0000000..d7e39e6 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/IEvaluatableObservable.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive +{ + internal interface IEvaluatableObservable + { + IObservable Eval(); + } +} diff --git a/LibExternal/System.Reactive/Internal/ISafeObserver.cs b/LibExternal/System.Reactive/Internal/ISafeObserver.cs new file mode 100644 index 0000000..6f56d31 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/ISafeObserver.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive +{ + /// + /// Base interface for observers that can dispose of a resource on a terminal notification + /// or when disposed itself. + /// + /// + internal interface ISafeObserver : IObserver, IDisposable + { + void SetResource(IDisposable resource); + } +} diff --git a/LibExternal/System.Reactive/Internal/IdentitySink.cs b/LibExternal/System.Reactive/Internal/IdentitySink.cs new file mode 100644 index 0000000..ed13662 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/IdentitySink.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive +{ + internal abstract class IdentitySink : Sink + { + protected IdentitySink(IObserver observer) : base(observer) + { + } + + public override void OnNext(T value) + { + ForwardOnNext(value); + } + } +} diff --git a/LibExternal/System.Reactive/Internal/ImmutableList.cs b/LibExternal/System.Reactive/Internal/ImmutableList.cs new file mode 100644 index 0000000..f4ad42b --- /dev/null +++ b/LibExternal/System.Reactive/Internal/ImmutableList.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive +{ + internal sealed class ImmutableList + { + public static readonly ImmutableList Empty = new(); + + private readonly T[] _data; + + private ImmutableList() => _data = Array.Empty(); + + public ImmutableList(T[] data) => _data = data; + + public T[] Data => _data; + + public ImmutableList Add(T value) + { + var newData = new T[_data.Length + 1]; + + Array.Copy(_data, newData, _data.Length); + newData[_data.Length] = value; + + return new ImmutableList(newData); + } + + public ImmutableList Remove(T value) + { + var i = Array.IndexOf(_data, value); + if (i < 0) + { + return this; + } + + var length = _data.Length; + if (length == 1) + { + return Empty; + } + + var newData = new T[length - 1]; + + Array.Copy(_data, 0, newData, 0, i); + Array.Copy(_data, i + 1, newData, i, length - i - 1); + + return new ImmutableList(newData); + } + } +} diff --git a/LibExternal/System.Reactive/Internal/Lookup.cs b/LibExternal/System.Reactive/Internal/Lookup.cs new file mode 100644 index 0000000..74e8c11 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/Lookup.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +#nullable disable // TODO: Substitute for implementation that doesn't use DictionaryK, V>. + +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace System.Reactive +{ + internal sealed class Lookup : ILookup + { + private readonly Dictionary> _dictionary; + + public Lookup(IEqualityComparer comparer) + { + _dictionary = new Dictionary>(comparer); + } + + public void Add(K key, E element) + { + if (!_dictionary.TryGetValue(key, out var list)) + { + _dictionary[key] = list = new List(); + } + + list.Add(element); + } + + public bool Contains(K key) => _dictionary.ContainsKey(key); + + public int Count => _dictionary.Count; + + public IEnumerable this[K key] + { + get + { + if (!_dictionary.TryGetValue(key, out var list)) + { + return Enumerable.Empty(); + } + + return Hide(list); + } + } + + // Instead of yielding the elements in a foreach loop, zero + // elements are skipped. Technically the result is the same + // with the benefit that the LINQ implementation can internally + // generate a type that keeps track of count of the enumerable. + // Consecutive operators can benefit from that knowledge. + private static IEnumerable Hide(List elements) => elements.Skip(0); + + public IEnumerator> GetEnumerator() + { + foreach (var kv in _dictionary) + { + yield return new Grouping(kv); + } + } + + private sealed class Grouping : IGrouping + { + private readonly KeyValuePair> _keyValuePair; + + public Grouping(KeyValuePair> keyValuePair) + { + _keyValuePair = keyValuePair; + } + + public K Key => _keyValuePair.Key; + + public IEnumerator GetEnumerator() => _keyValuePair.Value.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/LibExternal/System.Reactive/Internal/Map.cs b/LibExternal/System.Reactive/Internal/Map.cs new file mode 100644 index 0000000..1a636ac --- /dev/null +++ b/LibExternal/System.Reactive/Internal/Map.cs @@ -0,0 +1,78 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +#nullable disable + +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; + +namespace System.Reactive +{ + internal sealed class Map + { + // Taken from ConcurrentDictionary in the BCL. + + // The default concurrency level is DEFAULT_CONCURRENCY_MULTIPLIER * #CPUs. The higher the + // DEFAULT_CONCURRENCY_MULTIPLIER, the more concurrent writes can take place without interference + // and blocking, but also the more expensive operations that require all locks become (e.g. table + // resizing, ToArray, Count, etc). According to brief benchmarks that we ran, 4 seems like a good + // compromise. + private const int DefaultConcurrencyMultiplier = 4; + + private static int DefaultConcurrencyLevel => DefaultConcurrencyMultiplier * Environment.ProcessorCount; + + private readonly ConcurrentDictionary _map; + + public Map(int? capacity, IEqualityComparer comparer) + { + if (capacity.HasValue) + { + _map = new ConcurrentDictionary(DefaultConcurrencyLevel, capacity.Value, comparer); + } + else + { + _map = new ConcurrentDictionary(comparer); + } + } + + public TValue GetOrAdd(TKey key, Func valueFactory, out bool added) + { + added = false; + + TValue value; + var newValue = default(TValue); + var hasNewValue = false; + while (true) + { + if (_map.TryGetValue(key, out value)) + { + break; + } + + if (!hasNewValue) + { + newValue = valueFactory(); + hasNewValue = true; + } + + if (_map.TryAdd(key, newValue)) + { + added = true; + value = newValue; + break; + } + } + + return value; + } + + public IEnumerable Values => _map.Values.ToArray(); + + public bool Remove(TKey key) + { + return _map.TryRemove(key, out _); + } + } +} diff --git a/LibExternal/System.Reactive/Internal/NopObserver.cs b/LibExternal/System.Reactive/Internal/NopObserver.cs new file mode 100644 index 0000000..beed4e5 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/NopObserver.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive +{ + internal sealed class NopObserver : IObserver + { + public static readonly IObserver Instance = new NopObserver(); + + public void OnCompleted() { } + + public void OnError(Exception error) { } + + public void OnNext(T value) { } + } +} diff --git a/LibExternal/System.Reactive/Internal/PlatformEnlightenmentProvider.cs b/LibExternal/System.Reactive/Internal/PlatformEnlightenmentProvider.cs new file mode 100644 index 0000000..eb36798 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/PlatformEnlightenmentProvider.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; + +namespace System.Reactive.PlatformServices +{ + /// + /// (Infrastructure) Interface for enlightenment providers. + /// + /// + /// This type is used by the Rx infrastructure and not meant for public consumption or implementation. + /// No guarantees are made about forward compatibility of the type's functionality and its usage. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public interface IPlatformEnlightenmentProvider + { + /// + /// (Infrastructure) Tries to gets the specified service. + /// + /// Service type. + /// Optional set of arguments. + /// Service instance or null if not found. + T? GetService(params object[] args) where T : class; + } + + /// + /// (Infrastructure) Provider for platform-specific framework enlightenments. + /// + /// + /// This type is used by the Rx infrastructure and not meant for public consumption or implementation. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class PlatformEnlightenmentProvider + { + private static IPlatformEnlightenmentProvider _current = CreatePlatformProvider(); + + // + // NOTE TO MAINTAINERS + // + // Do *NOT* remove this mechanism which has been used beyond its original goal of supporting dependency injection when we were unifying + // the different Rx implementations as one Portable Library with platform-specific PlatformServices assemblies on top. Besides this, it + // has been used (and is still being used) for hosting scenarios where the host wants control over thread creation (cf. CAL), or other + // services (e.g. interception of query operators for logging, debugging, quota management, policy injection, etc.). Compat matters and + // lack thereof results in users getting stuck on older versions or having to resort to forking. + // + + /// + /// (Infrastructure) Gets the current enlightenment provider. If none is loaded yet, accessing this property triggers provider resolution. + /// + /// + /// This member is used by the Rx infrastructure and not meant for public consumption or implementation. + /// + public static IPlatformEnlightenmentProvider Current + { + get => _current; + set => _current = value ?? throw new ArgumentNullException(nameof(value)); + } + + private static IPlatformEnlightenmentProvider CreatePlatformProvider() => new CurrentPlatformEnlightenmentProvider(); + } +} diff --git a/LibExternal/System.Reactive/Internal/PriorityQueue.cs b/LibExternal/System.Reactive/Internal/PriorityQueue.cs new file mode 100644 index 0000000..70cc875 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/PriorityQueue.cs @@ -0,0 +1,166 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive +{ + internal sealed class PriorityQueue where T : IComparable + { + private long _count = long.MinValue; + private IndexedItem[] _items; + private int _size; + + public PriorityQueue() + : this(16) + { + } + + public PriorityQueue(int capacity) + { + _items = new IndexedItem[capacity]; + _size = 0; + } + + private bool IsHigherPriority(int left, int right) + { + return _items[left].CompareTo(_items[right]) < 0; + } + + private int Percolate(int index) + { + if (index >= _size || index < 0) + { + return index; + } + + var parent = (index - 1) / 2; + while (parent >= 0 && parent != index && IsHigherPriority(index, parent)) + { + // swap index and parent + (_items[parent], _items[index]) = (_items[index], _items[parent]); + index = parent; + parent = (index - 1) / 2; + } + + return index; + } + + private void Heapify(int index) + { + if (index >= _size || index < 0) + { + return; + } + + while (true) + { + var left = 2 * index + 1; + var right = 2 * index + 2; + var first = index; + + if (left < _size && IsHigherPriority(left, first)) + { + first = left; + } + + if (right < _size && IsHigherPriority(right, first)) + { + first = right; + } + + if (first == index) + { + break; + } + + // swap index and first + (_items[first], _items[index]) = (_items[index], _items[first]); + index = first; + } + } + + public int Count => _size; + + public T Peek() + { + if (_size == 0) + { + throw new InvalidOperationException(Strings_Core.HEAP_EMPTY); + } + + return _items[0].Value; + } + + private void RemoveAt(int index) + { + _items[index] = _items[--_size]; + _items[_size] = default; + + if (Percolate(index) == index) + { + Heapify(index); + } + + if (_size < _items.Length / 4) + { + var temp = _items; + _items = new IndexedItem[_items.Length / 2]; + Array.Copy(temp, 0, _items, 0, _size); + } + } + + public T Dequeue() + { + var result = Peek(); + RemoveAt(0); + return result; + } + + public void Enqueue(T item) + { + if (_size >= _items.Length) + { + var temp = _items; + _items = new IndexedItem[_items.Length * 2]; + Array.Copy(temp, _items, temp.Length); + } + + var index = _size++; + _items[index] = new IndexedItem { Value = item, Id = ++_count }; + Percolate(index); + } + + public bool Remove(T item) + { + for (var i = 0; i < _size; ++i) + { + if (EqualityComparer.Default.Equals(_items[i].Value, item)) + { + RemoveAt(i); + return true; + } + } + + return false; + } + + private struct IndexedItem : IComparable + { + public T Value; + public long Id; + + public int CompareTo(IndexedItem other) + { + var c = Value.CompareTo(other.Value); + if (c == 0) + { + c = Id.CompareTo(other.Id); + } + + return c; + } + } + } +} diff --git a/LibExternal/System.Reactive/Internal/Producer.cs b/LibExternal/System.Reactive/Internal/Producer.cs new file mode 100644 index 0000000..e52681b --- /dev/null +++ b/LibExternal/System.Reactive/Internal/Producer.cs @@ -0,0 +1,139 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reactive.Disposables; + +namespace System.Reactive +{ + /// + /// Interface with variance annotation; allows for better type checking when detecting capabilities in SubscribeSafe. + /// + /// Type of the resulting sequence's elements. + internal interface IProducer : IObservable + { + IDisposable SubscribeRaw(IObserver observer, bool enableSafeguard); + } + + /// + /// Base class for implementation of query operators, providing performance benefits over the use of Observable.Create. + /// + /// Type of the resulting sequence's elements. + internal abstract class BasicProducer : IProducer + { + /// + /// Publicly visible Subscribe method. + /// + /// Observer to send notifications on. The implementation of a producer must ensure the correct message grammar on the observer. + /// IDisposable to cancel the subscription. This causes the underlying sink to be notified of unsubscription, causing it to prevent further messages from being sent to the observer. + public IDisposable Subscribe(IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + return SubscribeRaw(observer, enableSafeguard: true); + } + + public IDisposable SubscribeRaw(IObserver observer, bool enableSafeguard) + { + IDisposable run; + ISafeObserver? safeObserver = null; + + // + // See AutoDetachObserver.cs for more information on the safeguarding requirement and + // its implementation aspects. + // + if (enableSafeguard) + { + observer = safeObserver = SafeObserver.Wrap(observer); + } + + if (CurrentThreadScheduler.IsScheduleRequired) + { + var runAssignable = new SingleAssignmentDisposable(); + + CurrentThreadScheduler.Instance.ScheduleAction( + (@this: this, runAssignable, observer), + static tuple => tuple.runAssignable.Disposable = tuple.@this.Run(tuple.observer)); + + run = runAssignable; + } + else + { + run = Run(observer); + } + + safeObserver?.SetResource(run); + return run; + } + + /// + /// Core implementation of the query operator, called upon a new subscription to the producer object. + /// + /// Observer to send notifications on. The implementation of a producer must ensure the correct message grammar on the observer. + /// Disposable representing all the resources and/or subscriptions the operator uses to process events. + /// The observer passed in to this method is not protected using auto-detach behavior upon an OnError or OnCompleted call. The implementation must ensure proper resource disposal and enforce the message grammar. + protected abstract IDisposable Run(IObserver observer); + } + + internal abstract class Producer : IProducer + where TSink : IDisposable + { + /// + /// Publicly visible Subscribe method. + /// + /// Observer to send notifications on. The implementation of a producer must ensure the correct message grammar on the observer. + /// IDisposable to cancel the subscription. This causes the underlying sink to be notified of unsubscription, causing it to prevent further messages from being sent to the observer. + public IDisposable Subscribe(IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + return SubscribeRaw(observer, enableSafeguard: true); + } + + public IDisposable SubscribeRaw(IObserver observer, bool enableSafeguard) + { + ISafeObserver? safeObserver = null; + + // + // See AutoDetachObserver.cs for more information on the safeguarding requirement and + // its implementation aspects. + // + if (enableSafeguard) + { + observer = safeObserver = SafeObserver.Wrap(observer); + } + + var sink = CreateSink(observer); + + safeObserver?.SetResource(sink); + + if (CurrentThreadScheduler.IsScheduleRequired) + { + CurrentThreadScheduler.Instance.ScheduleAction( + (@this: this, sink), + static tuple => tuple.@this.Run(tuple.sink)); + } + else + { + Run(sink); + } + + return sink; + } + + /// + /// Core implementation of the query operator, called upon a new subscription to the producer object. + /// + /// The sink object. + protected abstract void Run(TSink sink); + + protected abstract TSink CreateSink(IObserver observer); + } +} diff --git a/LibExternal/System.Reactive/Internal/PushPullAdapter.cs b/LibExternal/System.Reactive/Internal/PushPullAdapter.cs new file mode 100644 index 0000000..15c3588 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/PushPullAdapter.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +#if NO_PERF +using System.Collections.Generic; + +namespace System.Reactive +{ + sealed class PushPullAdapter : IObserver, IEnumerator + { + Action> yield; + Action dispose; + Func> moveNext; + Notification current; + bool done = false; + bool disposed; + + public PushPullAdapter(Action> yield, Func> moveNext, Action dispose) + { + this.yield = yield; + this.moveNext = moveNext; + this.dispose = dispose; + } + + public void OnNext(T value) + { + yield(Notification.CreateOnNext(value)); + } + + public void OnError(Exception exception) + { + yield(Notification.CreateOnError(exception)); + dispose(); + } + + public void OnCompleted() + { + yield(Notification.CreateOnCompleted()); + dispose(); + } + + public R Current + { + get { return current.Value; } + } + + public void Dispose() + { + disposed = true; + dispose(); + } + + object System.Collections.IEnumerator.Current + { + get { return this.Current; } + } + + public bool MoveNext() + { + if (disposed) + throw new ObjectDisposedException(""); + + if (!done) + { + current = moveNext(); + done = current.Kind != NotificationKind.OnNext; + } + + current.Exception?.Throw(); + + return current.HasValue; + } + + public void Reset() + { + throw new NotSupportedException(); + } + } +} +#endif diff --git a/LibExternal/System.Reactive/Internal/QueryServices.cs b/LibExternal/System.Reactive/Internal/QueryServices.cs new file mode 100644 index 0000000..608fb90 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/QueryServices.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.PlatformServices; + +namespace System.Reactive.Linq +{ + internal static class QueryServices + { + private static readonly IQueryServices Services = Initialize(); + + public static T GetQueryImpl(T defaultInstance) => Services.Extend(defaultInstance); + + private static IQueryServices Initialize() + { + return PlatformEnlightenmentProvider.Current.GetService() ?? new DefaultQueryServices(); + } + } + + internal interface IQueryServices + { + T Extend(T baseImpl); + } + + internal sealed class DefaultQueryServices : IQueryServices + { + public T Extend(T baseImpl) => baseImpl; + } +} diff --git a/LibExternal/System.Reactive/Internal/ReflectionUtils.cs b/LibExternal/System.Reactive/Internal/ReflectionUtils.cs new file mode 100644 index 0000000..75986a1 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/ReflectionUtils.cs @@ -0,0 +1,139 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Reflection; + +namespace System.Reactive +{ + internal static class ReflectionUtils + { + public static TDelegate CreateDelegate(object o, MethodInfo method) + { + return (TDelegate)(object)method.CreateDelegate(typeof(TDelegate), o); + } + + public static Delegate CreateDelegate(Type delegateType, object o, MethodInfo method) + { + return method.CreateDelegate(delegateType, o); + } + +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public static void GetEventMethods(Type targetType, object? target, string eventName, out MethodInfo addMethod, out MethodInfo removeMethod, out Type delegateType, out bool isWinRT) + { + EventInfo? e; + + if (target == null) + { + e = targetType.GetEvent(eventName, BindingFlags.Public | BindingFlags.Static); + if (e == null) + { + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings_Linq.COULD_NOT_FIND_STATIC_EVENT, eventName, targetType.FullName)); + } + } + else + { + e = targetType.GetEvent(eventName, BindingFlags.Public | BindingFlags.Instance); + if (e == null) + { + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings_Linq.COULD_NOT_FIND_INSTANCE_EVENT, eventName, targetType.FullName)); + } + } + + addMethod = e.GetAddMethod() ?? throw new InvalidOperationException(Strings_Linq.EVENT_MISSING_ADD_METHOD); + removeMethod = e.GetRemoveMethod() ?? throw new InvalidOperationException(Strings_Linq.EVENT_MISSING_REMOVE_METHOD); + + var psa = addMethod.GetParameters(); + if (psa.Length != 1) + { + throw new InvalidOperationException(Strings_Linq.EVENT_ADD_METHOD_SHOULD_TAKE_ONE_PARAMETER); + } + + var psr = removeMethod.GetParameters(); + if (psr.Length != 1) + { + throw new InvalidOperationException(Strings_Linq.EVENT_REMOVE_METHOD_SHOULD_TAKE_ONE_PARAMETER); + } + + isWinRT = false; + + if (IsWinRTEventRegistrationTokenType(addMethod.ReturnType)) + { + isWinRT = true; + + var pet = psr[0]; + if (IsWinRTEventRegistrationTokenType(pet.ParameterType)) + { + throw new InvalidOperationException(Strings_Linq.EVENT_WINRT_REMOVE_METHOD_SHOULD_TAKE_ERT); + } + } + + delegateType = psa[0].ParameterType; + + var invokeMethod = delegateType.GetMethod("Invoke")!; // NB: Delegates always have an Invoke method. + + var parameters = invokeMethod.GetParameters(); + + if (parameters.Length != 2) + { + throw new InvalidOperationException(Strings_Linq.EVENT_PATTERN_REQUIRES_TWO_PARAMETERS); + } + + if (!typeof(TSender).IsAssignableFrom(parameters[0].ParameterType)) + { + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings_Linq.EVENT_SENDER_NOT_ASSIGNABLE, typeof(TSender).FullName)); + } + + if (!typeof(TEventArgs).IsAssignableFrom(parameters[1].ParameterType)) + { + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings_Linq.EVENT_ARGS_NOT_ASSIGNABLE, typeof(TEventArgs).FullName)); + } + + if (invokeMethod.ReturnType != typeof(void)) + { + throw new InvalidOperationException(Strings_Linq.EVENT_MUST_RETURN_VOID); + } + } + + /// + /// Determine whether a type represents a WinRT event registration token + /// (https://learn.microsoft.com/en-us/uwp/api/windows.foundation.eventregistrationtoken). + /// + /// The type to check. + /// True if this represents a WinRT event registration token + /// + /// + /// We used to perform a simple comparison with typeof(EventRegistrationToken), but the + /// introduction of C#/WinRT has made this problematic. Before C#/WinRT, the .NET + /// projection of WinRT's Windows.Foundation.EventRegistrationToken type was + /// System.Runtime.InteropServices.WindowsRuntime.EventRegistrationToken. But that type is + /// associated with the old WinRT interop mechanisms in which the CLR works directly with + /// WinMD. That was how it worked up as far as .NET Core 3.1, and it's still how .NET + /// Framework works, but this direct WinMD support was removed in .NET 5.0. + /// + /// + /// If you're on .NET 5.0 or later, the System.Runtime.InteropServices.WindowsRuntime types + /// are no longer supported. While you can still get access to them through the NuGet + /// package of the same name (that's how .NET Standard 2.0 libraries are able to use these + /// types) they are best avoided, because the types in that library are no longer the types + /// you see when any of the WinRT types they are meant to represent are projected into the + /// CLR's world. + /// + /// + /// It was therefore necessary for Rx to stop using these types, and to drop its reference + /// to the System.Runtime.InteropServices.WindowsRuntime package. We can replicate the + /// same logic by looking for the type name. By checking for either the old or new + /// namespaces, we can support both the old projection (still used on .NET Framework) and + /// also the new C#/WinRT projection. + /// + /// + private static bool IsWinRTEventRegistrationTokenType(Type t) => + t.Name == "EventRegistrationToken" && + (t.Namespace == "System.Runtime.InteropServices.WindowsRuntime" || + t.Namespace == "WinRT"); + } +} diff --git a/LibExternal/System.Reactive/Internal/SafeObserver.cs b/LibExternal/System.Reactive/Internal/SafeObserver.cs new file mode 100644 index 0000000..869a112 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/SafeObserver.cs @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; + +namespace System.Reactive +{ + // + // See AutoDetachObserver.cs for more information on the safeguarding requirement and + // its implementation aspects. + // + + internal abstract class SafeObserver : ISafeObserver + { + private sealed class WrappingSafeObserver : SafeObserver + { + private readonly IObserver _observer; + + public WrappingSafeObserver(IObserver observer) + { + _observer = observer; + } + + public override void OnNext(TSource value) + { + var noError = false; + try + { + _observer.OnNext(value); + noError = true; + } + finally + { + if (!noError) + { + Dispose(); + } + } + } + + public override void OnError(Exception error) + { + using (this) + { + _observer.OnError(error); + } + } + + public override void OnCompleted() + { + using (this) + { + _observer.OnCompleted(); + } + } + } + + public static ISafeObserver Wrap(IObserver observer) + { + if (observer is AnonymousObserver a) + { + return a.MakeSafe(); + } + + return new WrappingSafeObserver(observer); + } + + private SingleAssignmentDisposableValue _disposable; + + public abstract void OnNext(TSource value); + + public abstract void OnError(Exception error); + + public abstract void OnCompleted(); + + public void SetResource(IDisposable resource) + { + _disposable.Disposable = resource; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _disposable.Dispose(); + } + } + } +} diff --git a/LibExternal/System.Reactive/Internal/ScheduledObserver.cs b/LibExternal/System.Reactive/Internal/ScheduledObserver.cs new file mode 100644 index 0000000..eb08ae4 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/ScheduledObserver.cs @@ -0,0 +1,762 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reactive.Disposables; +using System.Threading; + +namespace System.Reactive +{ + using Collections.Concurrent; + using Diagnostics; + + internal class ScheduledObserver : ObserverBase, IScheduledObserver + { + private int _state; + private const int Stopped = 0; + private const int Running = 1; + private const int Pending = 2; + private const int Faulted = 9; + private readonly ConcurrentQueue _queue = new(); + private bool _failed; + private Exception? _error; + private bool _completed; + private readonly IObserver _observer; + private readonly IScheduler _scheduler; + private readonly ISchedulerLongRunning? _longRunning; + private SerialDisposableValue _disposable; + + public ScheduledObserver(IScheduler scheduler, IObserver observer) + { + _scheduler = scheduler; + _observer = observer; + _longRunning = _scheduler.AsLongRunning(); + + if (_longRunning != null) + { + _dispatcherEvent = new SemaphoreSlim(0); + _dispatcherEventRelease = new SemaphoreSlimRelease(_dispatcherEvent); + } + } + + private sealed class SemaphoreSlimRelease : IDisposable + { + private volatile SemaphoreSlim? _dispatcherEvent; + + public SemaphoreSlimRelease(SemaphoreSlim dispatcherEvent) + { + _dispatcherEvent = dispatcherEvent; + } + + public void Dispose() + { + Interlocked.Exchange(ref _dispatcherEvent, null)?.Release(); + } + } + + private readonly object _dispatcherInitGate = new(); + private readonly SemaphoreSlim? _dispatcherEvent; + private readonly IDisposable? _dispatcherEventRelease; + private IDisposable? _dispatcherJob; + + private void EnsureDispatcher() + { + if (_dispatcherJob == null) + { + lock (_dispatcherInitGate) + { + if (_dispatcherJob == null) + { + _dispatcherJob = _longRunning!.ScheduleLongRunning(Dispatch); // NB: Only reachable when long-running. + + _disposable.Disposable = StableCompositeDisposable.Create + ( + _dispatcherJob, + _dispatcherEventRelease! + ); + } + } + } + } + + private void Dispatch(ICancelable cancel) + { + while (true) + { + _dispatcherEvent!.Wait(); // NB: If long-running, the event is set. + + if (cancel.IsDisposed) + { + return; + } + + while (_queue.TryDequeue(out var next)) + { + try + { + _observer.OnNext(next); + } + catch + { + while (_queue.TryDequeue(out _)) + { + } + + throw; + } + + _dispatcherEvent.Wait(); + + if (cancel.IsDisposed) + { + return; + } + } + + if (_failed) + { + _observer.OnError(_error!); + Dispose(); + return; + } + + if (_completed) + { + _observer.OnCompleted(); + Dispose(); + return; + } + } + } + + public void EnsureActive() => EnsureActive(1); + + public void EnsureActive(int n) + { + if (_longRunning != null) + { + if (n > 0) + { + _dispatcherEvent!.Release(n); // NB: If long-running, the event is set. + } + + EnsureDispatcher(); + } + else + { + EnsureActiveSlow(); + } + } + + private void EnsureActiveSlow() + { + var isOwner = false; + + while (true) + { + var old = Interlocked.CompareExchange(ref _state, Running, Stopped); + if (old == Stopped) + { + isOwner = true; // RUNNING + break; + } + + if (old == Faulted) + { + return; + } + + // If we find the consumer loop running, we transition to PENDING to handle + // the case where the queue is seen empty by the consumer, making it transition + // to the STOPPED state, but we inserted an item into the queue. + // + // C: _queue.TryDequeue == false (RUNNING) + // ---------------------------------------------- + // P: _queue.Enqueue(...) + // EnsureActive + // Exchange(ref _state, RUNNING) == RUNNING + // ---------------------------------------------- + // C: transition to STOPPED (STOPPED) + // + // In this case, P would believe C is running and not invoke the scheduler + // using the isOwner flag. + // + // By introducing an intermediate PENDING state and using CAS in the consumer + // to only transition to STOPPED in case we were still RUNNING, we can force + // the consumer to reconsider the decision to transition to STOPPED. In that + // case, the consumer loops again and re-reads from the queue and other state + // fields. At least one bit of state will have changed because EnsureActive + // should only be called after invocation of IObserver methods that touch + // this state. + // + if (old == Pending || old == Running && Interlocked.CompareExchange(ref _state, Pending, Running) == Running) + { + break; + } + } + + if (isOwner) + { + _disposable.Disposable = _scheduler.Schedule(null, Run); + } + } + + private void Run(object? state, Action recurse) + { + T? next; + + while (!_queue.TryDequeue(out next)) + { + if (_failed) + { + // Between transitioning to _failed and the queue check in the loop, + // items could have been queued, so we can't stop yet. We don't spin + // and immediately re-check the queue. + // + // C: _queue.TryDequeue == false + // ---------------------------------------------- + // P: OnNext(...) + // _queue.Enqueue(...) // Will get lost + // P: OnError(...) + // _failed = true + // ---------------------------------------------- + // C: if (_failed) + // _observer.OnError(...) // Lost an OnNext + // + if (!_queue.IsEmpty) + { + continue; + } + + Interlocked.Exchange(ref _state, Stopped); + _observer.OnError(_error!); + Dispose(); + return; + } + + if (_completed) + { + // Between transitioning to _completed and the queue check in the loop, + // items could have been queued, so we can't stop yet. We don't spin + // and immediately re-check the queue. + // + // C: _queue.TryDequeue == false + // ---------------------------------------------- + // P: OnNext(...) + // _queue.Enqueue(...) // Will get lost + // P: OnCompleted(...) + // _completed = true + // ---------------------------------------------- + // C: if (_completed) + // _observer.OnCompleted() // Lost an OnNext + // + if (!_queue.IsEmpty) + { + continue; + } + + Interlocked.Exchange(ref _state, Stopped); + _observer.OnCompleted(); + Dispose(); + return; + } + + var old = Interlocked.CompareExchange(ref _state, Stopped, Running); + if (old == Running || old == Faulted) + { + return; + } + + Debug.Assert(old == Pending); + + // The producer has put us in the PENDING state to prevent us from + // transitioning to STOPPED, so we go RUNNING again and re-check our state. + _state = Running; + } + + Interlocked.Exchange(ref _state, Running); + + try + { + _observer.OnNext(next); + } + catch + { + Interlocked.Exchange(ref _state, Faulted); + + while (_queue.TryDequeue(out _)) + { + } + + throw; + } + + recurse(state); + } + + protected override void OnNextCore(T value) + { + _queue.Enqueue(value); + } + + protected override void OnErrorCore(Exception exception) + { + _error = exception; + _failed = true; + } + + protected override void OnCompletedCore() + { + _completed = true; + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _disposable.Dispose(); + } + } + } + + internal sealed class ObserveOnObserver : ScheduledObserver + { + private SingleAssignmentDisposableValue _run; + + public ObserveOnObserver(IScheduler scheduler, IObserver observer) + : base(scheduler, observer) + { + + } + + public void Run(IObservable source) + { + _run.Disposable = source.SubscribeSafe(this); + } + + protected override void OnNextCore(T value) + { + base.OnNextCore(value); + EnsureActive(); + } + + protected override void OnErrorCore(Exception exception) + { + base.OnErrorCore(exception); + EnsureActive(); + } + + protected override void OnCompletedCore() + { + base.OnCompletedCore(); + EnsureActive(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _run.Dispose(); + } + } + } + + internal interface IScheduledObserver : IObserver, IDisposable + { + void EnsureActive(); + void EnsureActive(int count); + } + + /// + /// An ObserveOn operator implementation that uses lock-free + /// techniques to signal events to the downstream. + /// + /// The element type of the sequence. + internal sealed class ObserveOnObserverNew : IdentitySink + { + private readonly IScheduler _scheduler; + + private readonly ConcurrentQueue _queue; + + /// + /// The current task representing a running drain operation. + /// + private IDisposable? _task; + + /// + /// Indicates the work-in-progress state of this operator, + /// zero means no work is currently being done. + /// + private int _wip; + + /// + /// If true, the upstream has issued OnCompleted. + /// + private bool _done; + + /// + /// If is true and this is non-null, the upstream + /// failed with an OnError. + /// + private Exception? _error; + + /// + /// Indicates a dispose has been requested. + /// + private bool _disposed; + + public ObserveOnObserverNew(IScheduler scheduler, IObserver downstream) : base(downstream) + { + _scheduler = scheduler; + _queue = new ConcurrentQueue(); + } + + protected override void Dispose(bool disposing) + { + Volatile.Write(ref _disposed, true); + + base.Dispose(disposing); + if (disposing) + { + Disposable.Dispose(ref _task); + Clear(_queue); + } + } + + /// + /// Remove remaining elements from the queue upon + /// cancellation or failure. + /// + /// The queue to use. The argument ensures that the + /// _queue field is not re-read from memory unnecessarily + /// due to the memory barriers inside TryDequeue mandating it + /// despite the field is read-only. + private static void Clear(ConcurrentQueue q) + { + while (q.TryDequeue(out var _)) + { + } + } + + public override void OnCompleted() + { + Volatile.Write(ref _done, true); + Schedule(); + } + + public override void OnError(Exception error) + { + _error = error; + Volatile.Write(ref _done, true); + Schedule(); + } + + public override void OnNext(T value) + { + _queue.Enqueue(value); + Schedule(); + } + + /// + /// Submit the drain task via the appropriate scheduler if + /// there is no drain currently running (wip > 0). + /// + private void Schedule() + { + if (Interlocked.Increment(ref _wip) == 1) + { + var newTask = new SingleAssignmentDisposable(); + + if (Disposable.TrySetMultiple(ref _task, newTask)) + { + newTask.Disposable = _scheduler.Schedule(this, DrainShortRunningFunc); + } + + // If there was a cancellation, clear the queue + // of items. This doesn't have to be inside the + // wip != 0 (exclusive) mode as the queue + // is of a multi-consumer type. + if (Volatile.Read(ref _disposed)) + { + Clear(_queue); + } + } + } + + /// + /// The static action to be scheduled on a simple scheduler. + /// Avoids creating a delegate that captures this + /// whenever the signals have to be drained. + /// + private static readonly Func, IDisposable> DrainShortRunningFunc = + static (scheduler, self) => self.DrainShortRunning(scheduler); + + /// + /// Emits at most one signal per run on a scheduler that doesn't like + /// long running tasks. + /// + /// The scheduler to use for scheduling the next signal emission if necessary. + /// The IDisposable of the recursively scheduled task or an empty disposable. + private IDisposable DrainShortRunning(IScheduler recursiveScheduler) + { + DrainStep(_queue); + + if (Interlocked.Decrement(ref _wip) != 0) + { + // Don't return the disposable of Schedule() because that may chain together + // a long string of ScheduledItems causing StackOverflowException upon Dispose() + var d = recursiveScheduler.Schedule(this, DrainShortRunningFunc); + Disposable.TrySetMultiple(ref _task, d); + } + return Disposable.Empty; + } + + /// + /// Executes a drain step by checking the disposed state, + /// checking for the terminated state and for an + /// empty queue, issuing the appropriate signals to the + /// given downstream. + /// + /// The queue to use. The argument ensures that the + /// _queue field is not re-read from memory due to the memory barriers + /// inside TryDequeue mandating it despite the field is read-only. + /// In addition, the DrainStep is invoked from the DrainLongRunning's loop + /// so reading _queue inside this method would still incur the same barrier + /// overhead otherwise. + private void DrainStep(ConcurrentQueue q) + { + // Check if the operator has been disposed + if (Volatile.Read(ref _disposed)) + { + // cleanup residue items in the queue + Clear(q); + return; + } + + // Has the upstream call OnCompleted? + var d = Volatile.Read(ref _done); + + if (d) + { + // done = true happens before setting error + // this is safe to be a plain read + var ex = _error; + // if not null, there was an OnError call + if (ex != null) + { + Volatile.Write(ref _disposed, true); + ForwardOnError(ex); + return; + } + } + + // get the next item from the queue if any + if (q.TryDequeue(out var v)) + { + ForwardOnNext(v); + return; + } + + // the upstream called OnComplete and the queue is empty + // that means we are done, no further signals can happen + if (d) + { + Volatile.Write(ref _disposed, true); + // otherwise, complete normally + ForwardOnCompleted(); + } + } + } + + /// + /// Signals events on a ISchedulerLongRunning by blocking the emission thread while waiting + /// for them from the upstream. + /// + /// The element type of the sequence. + internal sealed class ObserveOnObserverLongRunning : IdentitySink + { + /// + /// This will run a suspending drain task, hogging the backing thread + /// until the sequence terminates or gets disposed. + /// + private readonly ISchedulerLongRunning _scheduler; + + /// + /// The queue for holding the OnNext items, terminal signals have their own fields. + /// + private readonly ConcurrentQueue _queue; + + /// + /// Protects the suspension and resumption of the long running drain task. + /// + private readonly object _suspendGuard; + + /// + /// The work-in-progress counter. If it jumps from 0 to 1, the drain task is resumed, + /// if it reaches 0 again, the drain task is suspended. + /// + private long _wip; + + /// + /// Set to true if the upstream terminated. + /// + private bool _done; + + /// + /// Set to a non-null Exception if the upstream terminated with OnError. + /// + private Exception? _error; + + /// + /// Indicates the sequence has been disposed and the drain task should quit. + /// + private bool _disposed; + + /// + /// Makes sure the drain task is scheduled only once, when the first signal + /// from upstream arrives. + /// + private int _runDrainOnce; + + /// + /// The disposable tracking the drain task. + /// + private SingleAssignmentDisposableValue _drainTask; + + public ObserveOnObserverLongRunning(ISchedulerLongRunning scheduler, IObserver observer) : base(observer) + { + _scheduler = scheduler; + _queue = new ConcurrentQueue(); + _suspendGuard = new object(); + } + + public override void OnCompleted() + { + Volatile.Write(ref _done, true); + Schedule(); + } + + public override void OnError(Exception error) + { + _error = error; + Volatile.Write(ref _done, true); + Schedule(); + } + + public override void OnNext(TSource value) + { + _queue.Enqueue(value); + Schedule(); + } + + private void Schedule() + { + // Schedule the suspending drain once + if (Volatile.Read(ref _runDrainOnce) == 0 + && Interlocked.CompareExchange(ref _runDrainOnce, 1, 0) == 0) + { + _drainTask.Disposable = _scheduler.ScheduleLongRunning(this, DrainLongRunning); + } + + // Indicate more work is to be done by the drain loop + if (Interlocked.Increment(ref _wip) == 1L) + { + // resume the drain loop waiting on the guard + lock (_suspendGuard) + { + Monitor.Pulse(_suspendGuard); + } + } + } + + /// + /// Static reference to the Drain method, saves allocation. + /// + private static readonly Action, ICancelable> DrainLongRunning = static (self, cancelable) => self.Drain(); + + protected override void Dispose(bool disposing) + { + // Indicate the drain task should quit + Volatile.Write(ref _disposed, true); + // Resume the drain task in case it sleeps + lock (_suspendGuard) + { + Monitor.Pulse(_suspendGuard); + } + // Cancel the drain task handle. + _drainTask.Dispose(); + base.Dispose(disposing); + } + + private void Drain() + { + var q = _queue; + for (; ; ) + { + // If the sequence was disposed, clear the queue and quit + if (Volatile.Read(ref _disposed)) + { + while (q.TryDequeue(out var _)) ; + break; + } + + // Has the upstream terminated? + var isDone = Volatile.Read(ref _done); + // Do we have an item in the queue + var hasValue = q.TryDequeue(out var item); + + // If the upstream has terminated and no further items are in the queue + if (isDone && !hasValue) + { + // Find out if the upstream terminated with an error and signal accordingly. + var e = _error; + if (e != null) + { + ForwardOnError(e); + } + else + { + ForwardOnCompleted(); + } + break; + } + + // There was an item, signal it. + if (hasValue) + { + ForwardOnNext(item!); + // Consume the item and try the next item if the work-in-progress + // indicator is still not zero + if (Interlocked.Decrement(ref _wip) != 0L) + { + continue; + } + } + + // If we run out of work and the sequence is not disposed + if (Volatile.Read(ref _wip) == 0L && !Volatile.Read(ref _disposed)) + { + var g = _suspendGuard; + // try sleeping, if we can't even enter the lock, the producer + // side is currently trying to resume us + if (Monitor.TryEnter(g)) + { + // Make sure again there is still no work and the sequence is not disposed + if (Volatile.Read(ref _wip) == 0L && !Volatile.Read(ref _disposed)) + { + // wait for a Pulse(g) + Monitor.Wait(g); + } + // Unlock + Monitor.Exit(g); + } + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Internal/Sink.cs b/LibExternal/System.Reactive/Internal/Sink.cs new file mode 100644 index 0000000..cc74d9b --- /dev/null +++ b/LibExternal/System.Reactive/Internal/Sink.cs @@ -0,0 +1,116 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; +using System.Threading; + +namespace System.Reactive +{ + internal interface ISink + { + void ForwardOnNext(TTarget value); + void ForwardOnCompleted(); + void ForwardOnError(Exception error); + } + + internal abstract class Sink : ISink, IDisposable + { + private SingleAssignmentDisposableValue _upstream; + private volatile IObserver _observer; + + protected Sink(IObserver observer) + { + _observer = observer; + } + + public void Dispose() + { + if (Interlocked.Exchange(ref _observer, NopObserver.Instance) != NopObserver.Instance) + Dispose(true); + } + + /// + /// Override this method to dispose additional resources. + /// The method is guaranteed to be called at most once. + /// + /// If true, the method was called from . + protected virtual void Dispose(bool disposing) + { + //Calling base.Dispose(true) is not a proper disposal, so we can omit the assignment here. + //Sink is internal so this can pretty much be enforced. + //_observer = NopObserver.Instance; + + _upstream.Dispose(); + } + + public void ForwardOnNext(TTarget value) + { + _observer.OnNext(value); + } + + public void ForwardOnCompleted() + { + _observer.OnCompleted(); + Dispose(); + } + + public void ForwardOnError(Exception error) + { + _observer.OnError(error); + Dispose(); + } + + protected void SetUpstream(IDisposable upstream) + { + _upstream.Disposable = upstream; + } + + protected void DisposeUpstream() + { + _upstream.Dispose(); + } + } + + /// + /// Base class for implementation of query operators, providing a lightweight sink that can be disposed to mute the outgoing observer. + /// + /// Type of the resulting sequence's elements. + /// + /// Implementations of sinks are responsible to enforce the message grammar on the associated observer. Upon sending a terminal message, a pairing Dispose call should be made to trigger cancellation of related resources and to mute the outgoing observer. + internal abstract class Sink : Sink, IObserver + { + protected Sink(IObserver observer) : base(observer) + { + } + + public virtual void Run(IObservable source) + { + SetUpstream(source.SubscribeSafe(this)); + } + + public abstract void OnNext(TSource value); + + public virtual void OnError(Exception error) => ForwardOnError(error); + + public virtual void OnCompleted() => ForwardOnCompleted(); + + public IObserver GetForwarder() => new _(this); + + private sealed class _ : IObserver + { + private readonly Sink _forward; + + public _(Sink forward) + { + _forward = forward; + } + + public void OnNext(TTarget value) => _forward.ForwardOnNext(value); + + public void OnError(Exception error) => _forward.ForwardOnError(error); + + public void OnCompleted() => _forward.ForwardOnCompleted(); + } + } +} diff --git a/LibExternal/System.Reactive/Internal/StopwatchImpl.cs b/LibExternal/System.Reactive/Internal/StopwatchImpl.cs new file mode 100644 index 0000000..b1ba236 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/StopwatchImpl.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace System.Reactive.Concurrency +{ + // + // WARNING: This code is kept *identically* in two places. One copy is kept in System.Reactive.Core for non-PLIB platforms. + // Another copy is kept in System.Reactive.PlatformServices to enlighten the default lowest common denominator + // behavior of Rx for PLIB when used on a more capable platform. + // + internal class /*Default*/StopwatchImpl : IStopwatch + { + private readonly Stopwatch _sw; + + public StopwatchImpl() => _sw = Stopwatch.StartNew(); + + public TimeSpan Elapsed => _sw.Elapsed; + } +} diff --git a/LibExternal/System.Reactive/Internal/Stubs.cs b/LibExternal/System.Reactive/Internal/Stubs.cs new file mode 100644 index 0000000..344f5f7 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/Stubs.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive +{ + internal static class Stubs + { + public static readonly Action Ignore = static _ => { }; + public static readonly Func I = static _ => _; + } + + internal static class Stubs + { + public static readonly Action Nop = static () => { }; + public static readonly Action Throw = static ex => { ex.Throw(); }; + } + + internal static class TimerStubs + { + public static readonly System.Threading.Timer Never = new(static _ => { }); + } +} + diff --git a/LibExternal/System.Reactive/Internal/SynchronizationContextExtensions.cs b/LibExternal/System.Reactive/Internal/SynchronizationContextExtensions.cs new file mode 100644 index 0000000..829b9d7 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/SynchronizationContextExtensions.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Threading; + +namespace System.Reactive.Concurrency +{ + internal static class SynchronizationContextExtensions + { + public static void PostWithStartComplete(this SynchronizationContext context, Action action, T state) + { + context.OperationStarted(); + + context.Post( + o => + { + try + { + action((T)o!); + } + finally + { + context.OperationCompleted(); + } + }, + state + ); + } + + public static void PostWithStartComplete(this SynchronizationContext context, Action action) + { + context.OperationStarted(); + + context.Post( + _ => + { + try + { + action(); + } + finally + { + context.OperationCompleted(); + } + }, + null + ); + } + } +} diff --git a/LibExternal/System.Reactive/Internal/SynchronizedObserver.cs b/LibExternal/System.Reactive/Internal/SynchronizedObserver.cs new file mode 100644 index 0000000..8709f49 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/SynchronizedObserver.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive +{ + internal sealed class SynchronizedObserver : ObserverBase + { + private readonly object _gate; + private readonly IObserver _observer; + + public SynchronizedObserver(IObserver observer, object gate) + { + _gate = gate; + _observer = observer; + } + + protected override void OnNextCore(T value) + { + lock (_gate) + { + _observer.OnNext(value); + } + } + + protected override void OnErrorCore(Exception exception) + { + lock (_gate) + { + _observer.OnError(exception); + } + } + + protected override void OnCompletedCore() + { + lock (_gate) + { + _observer.OnCompleted(); + } + } + } +} diff --git a/LibExternal/System.Reactive/Internal/SystemClock.Default.cs b/LibExternal/System.Reactive/Internal/SystemClock.Default.cs new file mode 100644 index 0000000..30aa30a --- /dev/null +++ b/LibExternal/System.Reactive/Internal/SystemClock.Default.cs @@ -0,0 +1,133 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Reactive.Concurrency; +using System.Reactive.Disposables; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Reactive.PlatformServices +{ + /// + /// (Infrastructure) Provides access to the local system clock. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public class DefaultSystemClock : ISystemClock + { + /// + /// Gets the current time. + /// + public DateTimeOffset UtcNow => DateTimeOffset.UtcNow; + } + + internal class DefaultSystemClockMonitor : PeriodicTimerSystemClockMonitor + { + private static readonly TimeSpan DefaultPeriod = TimeSpan.FromSeconds(1); + + public DefaultSystemClockMonitor() + : base(DefaultPeriod) + { + } + } + + /// + /// (Infrastructure) Monitors for system clock changes based on a periodic timer. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public class PeriodicTimerSystemClockMonitor : INotifySystemClockChanged + { + private readonly TimeSpan _period; + private SerialDisposableValue _timer; + + /// + /// Use the Unix milliseconds for the current time + /// so it can be atomically read/written without locking. + /// + private long _lastTimeUnixMillis; + + private EventHandler? _systemClockChanged; + + private const int SyncMaxRetries = 100; + private const double SyncMaxDelta = 10; + private const int MaxError = 100; + + /// + /// Creates a new monitor for system clock changes with the specified polling frequency. + /// + /// Polling frequency for system clock changes. + public PeriodicTimerSystemClockMonitor(TimeSpan period) + { + _period = period; + } + + /// + /// Event that gets raised when a system clock change is detected. + /// + public event EventHandler SystemClockChanged + { + add + { + NewTimer(); + + _systemClockChanged += value; + } + + remove + { + _systemClockChanged -= value; + + _timer.Disposable = Disposable.Empty; + } + } + + private void NewTimer() + { + _timer.Disposable = Disposable.Empty; + + var n = 0L; + for (; ; ) + { + var now = SystemClock.UtcNow.ToUnixTimeMilliseconds(); + Interlocked.Exchange(ref _lastTimeUnixMillis, now); + + _timer.Disposable = ConcurrencyAbstractionLayer.Current.StartPeriodicTimer(TimeChanged, _period); + + if (Math.Abs(SystemClock.UtcNow.ToUnixTimeMilliseconds() - now) <= SyncMaxDelta) + { + break; + } + if (_timer.Disposable == Disposable.Empty) + { + break; + } + if (++n >= SyncMaxRetries) + { + Task.Delay((int)SyncMaxDelta).Wait(); + } + } + } + + private void TimeChanged() + { + var newTime = SystemClock.UtcNow; + var now = newTime.ToUnixTimeMilliseconds(); + var last = Volatile.Read(ref _lastTimeUnixMillis); + + var oldTime = (long)(last + _period.TotalMilliseconds); + var diff = now - oldTime; + if (Math.Abs(diff) >= MaxError) + { + _systemClockChanged?.Invoke(this, new SystemClockChangedEventArgs( + DateTimeOffset.FromUnixTimeMilliseconds(oldTime), newTime)); + + NewTimer(); + } + else + { + Interlocked.Exchange(ref _lastTimeUnixMillis, SystemClock.UtcNow.ToUnixTimeMilliseconds()); + } + } + } +} diff --git a/LibExternal/System.Reactive/Internal/SystemClock.cs b/LibExternal/System.Reactive/Internal/SystemClock.cs new file mode 100644 index 0000000..373e93b --- /dev/null +++ b/LibExternal/System.Reactive/Internal/SystemClock.cs @@ -0,0 +1,220 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.ComponentModel; +using System.Reactive.Concurrency; +using System.Threading; + +namespace System.Reactive.PlatformServices +{ + /// + /// (Infrastructure) Provides access to local system clock services. + /// + /// + /// This type is used by the Rx infrastructure and not meant for public consumption or implementation. + /// No guarantees are made about forward compatibility of the type's functionality and its usage. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class SystemClock + { + private static readonly Lazy ServiceSystemClock = new(InitializeSystemClock); + private static readonly Lazy ServiceSystemClockChanged = new(InitializeSystemClockChanged); + internal static readonly HashSet> SystemClockChanged = new(); + private static IDisposable? _systemClockChangedHandlerCollector; + + private static int _refCount; + + /// + /// Gets the local system clock time. + /// + public static DateTimeOffset UtcNow => ServiceSystemClock.Value.UtcNow; + + /// + /// Adds a reference to the system clock monitor, causing it to be sending notifications. + /// + /// Thrown when the system doesn't support sending clock change notifications. + public static void AddRef() + { + if (Interlocked.Increment(ref _refCount) == 1) + { + ServiceSystemClockChanged.Value.SystemClockChanged += OnSystemClockChanged; + } + } + + /// + /// Removes a reference to the system clock monitor, causing it to stop sending notifications + /// if the removed reference was the last one. + /// + public static void Release() + { + if (Interlocked.Decrement(ref _refCount) == 0) + { + ServiceSystemClockChanged.Value.SystemClockChanged -= OnSystemClockChanged; + } + } + + internal static void OnSystemClockChanged(object? sender, SystemClockChangedEventArgs e) + { + lock (SystemClockChanged) + { + // create a defensive copy as the callbacks may change the hashset + var copySystemClockChanged = new List>(SystemClockChanged); + foreach (var entry in copySystemClockChanged) + { + if (entry.TryGetTarget(out var scheduler)) + { + scheduler.SystemClockChanged(sender, e); + } + } + } + } + + private static ISystemClock InitializeSystemClock() + { + return PlatformEnlightenmentProvider.Current.GetService() ?? new DefaultSystemClock(); + } + + private static INotifySystemClockChanged InitializeSystemClockChanged() + { + return PlatformEnlightenmentProvider.Current.GetService() ?? new DefaultSystemClockMonitor(); + } + + internal static void Register(LocalScheduler scheduler) + { + // + // LocalScheduler maintains per-instance work queues that need revisiting + // upon system clock changes. We need to be careful to avoid keeping those + // scheduler instances alive by the system clock monitor, so we use weak + // references here. In particular, AsyncLockScheduler in ImmediateScheduler + // can have a lot of instances, so we need to collect spurious handlers + // at regular times. + // + lock (SystemClockChanged) + { + SystemClockChanged.Add(new WeakReference(scheduler)); + + if (SystemClockChanged.Count == 1) + { + _systemClockChangedHandlerCollector = ConcurrencyAbstractionLayer.Current.StartPeriodicTimer(CollectHandlers, TimeSpan.FromSeconds(30)); + } + else if (SystemClockChanged.Count % 64 == 0) + { + CollectHandlers(); + } + } + } + + private static void CollectHandlers() + { + // + // The handler collector merely collects the WeakReference instances + // that are kept in the hash set. The underlying scheduler itself will + // be collected due to the weak reference. Unfortunately, we can't use + // the ConditionalWeakTable type here because we need to + // be able to enumerate the keys. + // + lock (SystemClockChanged) + { + HashSet>? remove = null; + + foreach (var handler in SystemClockChanged) + { + if (!handler.TryGetTarget(out _)) + { + remove ??= new HashSet>(); + + remove.Add(handler); + } + } + + if (remove != null) + { + foreach (var handler in remove) + { + SystemClockChanged.Remove(handler); + } + } + + if (SystemClockChanged.Count == 0) + { + _systemClockChangedHandlerCollector?.Dispose(); + _systemClockChangedHandlerCollector = null; + } + } + } + } + + /// + /// (Infrastructure) Provides access to the local system clock. + /// + /// + /// This type is used by the Rx infrastructure and not meant for public consumption or implementation. + /// No guarantees are made about forward compatibility of the type's functionality and its usage. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public interface ISystemClock + { + /// + /// Gets the current time. + /// + DateTimeOffset UtcNow { get; } + } + + /// + /// (Infrastructure) Provides a mechanism to notify local schedulers about system clock changes. + /// + /// + /// This type is used by the Rx infrastructure and not meant for public consumption or implementation. + /// No guarantees are made about forward compatibility of the type's functionality and its usage. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public interface INotifySystemClockChanged + { + /// + /// Event that gets raised when a system clock change is detected. + /// + event EventHandler SystemClockChanged; + } + + /// + /// (Infrastructure) Event arguments for system clock change notifications. + /// + /// + /// This type is used by the Rx infrastructure and not meant for public consumption or implementation. + /// No guarantees are made about forward compatibility of the type's functionality and its usage. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public class SystemClockChangedEventArgs : EventArgs + { + /// + /// Creates a new system clock notification object with unknown old and new times. + /// + public SystemClockChangedEventArgs() + : this(DateTimeOffset.MinValue, DateTimeOffset.MaxValue) + { + } + + /// + /// Creates a new system clock notification object with the specified old and new times. + /// + /// Time before the system clock changed, or DateTimeOffset.MinValue if not known. + /// Time after the system clock changed, or DateTimeOffset.MaxValue if not known. + public SystemClockChangedEventArgs(DateTimeOffset oldTime, DateTimeOffset newTime) + { + OldTime = oldTime; + NewTime = newTime; + } + + /// + /// Gets the time before the system clock changed, or DateTimeOffset.MinValue if not known. + /// + public DateTimeOffset OldTime { get; } + + /// + /// Gets the time after the system clock changed, or DateTimeOffset.MaxValue if not known. + /// + public DateTimeOffset NewTime { get; } + } +} diff --git a/LibExternal/System.Reactive/Internal/TailRecursiveSink.cs b/LibExternal/System.Reactive/Internal/TailRecursiveSink.cs new file mode 100644 index 0000000..0a97f08 --- /dev/null +++ b/LibExternal/System.Reactive/Internal/TailRecursiveSink.cs @@ -0,0 +1,255 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Reactive.Disposables; +using System.Threading; + +namespace System.Reactive +{ + internal abstract class TailRecursiveSink : IdentitySink + { + private readonly Stack>> _stack = new(); + + private bool _isDisposed; + private int _trampoline; + private IDisposable? _currentSubscription; + + protected TailRecursiveSink(IObserver observer) + : base(observer) + { + } + + public void Run(IEnumerable> sources) + { + if (!TryGetEnumerator(sources, out var current)) + { + return; + } + + _stack.Push(current); + + Drain(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + DisposeAll(); + } + + base.Dispose(disposing); + } + + private void Drain() + { + if (Interlocked.Increment(ref _trampoline) != 1) + { + return; + } + + for (; ; ) + { + if (Volatile.Read(ref _isDisposed)) + { + while (_stack.Count != 0) + { + var enumerator = _stack.Pop(); + enumerator.Dispose(); + } + + Disposable.Dispose(ref _currentSubscription); + } + else + { + if (_stack.Count != 0) + { + var currentEnumerator = _stack.Peek(); + + var currentObservable = default(IObservable); + + try + { + if (currentEnumerator.MoveNext()) + { + currentObservable = currentEnumerator.Current; + } + } + catch (Exception ex) + { + currentEnumerator.Dispose(); + ForwardOnError(ex); + Volatile.Write(ref _isDisposed, true); + continue; + } + + IObservable? next; + + try + { + next = Unpack(currentObservable); + } + catch (Exception ex) + { + if (!Fail(ex)) + { + Volatile.Write(ref _isDisposed, true); + } + continue; + } + + if (next != null) + { + var nextSeq = Extract(next); + if (nextSeq != null) + { + if (TryGetEnumerator(nextSeq, out var nextEnumerator)) + { + _stack.Push(nextEnumerator); + continue; + } + + Volatile.Write(ref _isDisposed, true); + continue; + } + + // we need an unique indicator for this as + // Subscribe could return a Disposable.Empty or + // a BooleanDisposable + var sad = ReadyToken.Ready; + + // Swap in the Ready indicator so we know the sequence hasn't been disposed + if (Disposable.TrySetSingle(ref _currentSubscription, sad) == TrySetSingleResult.Success) + { + // subscribe to the source + var d = next.SubscribeSafe(this); + + // Try to swap in the returned disposable in place of the Ready indicator + // Since this drain loop is the only one to use Ready, this should + // be unambiguous + var u = Interlocked.CompareExchange(ref _currentSubscription, d, sad); + + // sequence disposed or completed synchronously + if (u != sad) + { + d.Dispose(); + if (u == BooleanDisposable.True) + { + continue; + } + } + } + else + { + continue; + } + } + else + { + _stack.Pop(); + currentEnumerator.Dispose(); + continue; + } + } + else + { + Volatile.Write(ref _isDisposed, true); + Done(); + } + } + + if (Interlocked.Decrement(ref _trampoline) == 0) + { + break; + } + } + + static IObservable? Unpack(IObservable? source) + { + bool hasOpt; + + do + { + hasOpt = false; + + if (source is IEvaluatableObservable eval) + { + source = eval.Eval(); + hasOpt = true; + } + } while (hasOpt); + + return source; + } + } + + private void DisposeAll() + { + Volatile.Write(ref _isDisposed, true); + // the disposing of currentSubscription is deferred to drain due to some ObservableExTest.Iterate_Complete() + // Interlocked.Exchange(ref currentSubscription, BooleanDisposable.True)?.Dispose(); + Drain(); + } + + protected void Recurse() + { + if (Disposable.TrySetSerial(ref _currentSubscription, null)) + { + Drain(); + } + } + + protected abstract IEnumerable>? Extract(IObservable source); + + private bool TryGetEnumerator(IEnumerable> sources, [NotNullWhen(true)] out IEnumerator>? result) + { + try + { + result = sources.GetEnumerator(); + return true; + } + catch (Exception exception) + { + ForwardOnError(exception); + + result = null; + return false; + } + } + + protected virtual void Done() + { + ForwardOnCompleted(); + } + + protected virtual bool Fail(Exception error) + { + ForwardOnError(error); + + return false; + } + } + + /// + /// Holds onto a singleton IDisposable indicating a ready state. + /// + internal static class ReadyToken + { + /// + /// This indicates the operation has been prepared and ready for + /// the next step. + /// + internal static readonly IDisposable Ready = new ReadyDisposable(); + + private sealed class ReadyDisposable : IDisposable + { + public void Dispose() + { + // deliberately no-op + } + } + } +} diff --git a/LibExternal/System.Reactive/Internal/TaskExtensions.cs b/LibExternal/System.Reactive/Internal/TaskExtensions.cs new file mode 100644 index 0000000..eb32c1b --- /dev/null +++ b/LibExternal/System.Reactive/Internal/TaskExtensions.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Threading.Tasks +{ + internal static class TaskExtensions + { + public static Task ContinueWithState(this Task task, Action continuationAction, TState state, TaskContinuationOptions continuationOptions, CancellationToken cancellationToken) + { + return task.ContinueWith( + (t, tupleObject) => + { + var (closureAction, closureState) = ((Action, TState))tupleObject!; + + closureAction(t, closureState); + }, + (continuationAction, state), + cancellationToken, + continuationOptions, + TaskScheduler.Default); + } + + public static Task ContinueWithState(this Task task, Action, TState> continuationAction, TState state, CancellationToken cancellationToken) + { + return task.ContinueWith( + (t, tupleObject) => + { + var (closureAction, closureState) = ((Action, TState>, TState))tupleObject!; + + closureAction(t, closureState); + }, + (continuationAction, state), + cancellationToken); + } + + public static Task ContinueWithState(this Task task, Action, TState> continuationAction, TState state, TaskContinuationOptions continuationOptions, CancellationToken cancellationToken) + { + return task.ContinueWith( + (t, tupleObject) => + { + var (closureAction, closureState) = ((Action, TState>, TState))tupleObject!; + + closureAction(t, closureState); + }, + (continuationAction, state), + cancellationToken, + continuationOptions, + TaskScheduler.Default); + } + } +} diff --git a/LibExternal/System.Reactive/Joins/ActivePlan.cs b/LibExternal/System.Reactive/Joins/ActivePlan.cs new file mode 100644 index 0000000..aa4f3bb --- /dev/null +++ b/LibExternal/System.Reactive/Joins/ActivePlan.cs @@ -0,0 +1,1445 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive.Joins +{ + internal abstract class ActivePlan + { + private readonly Dictionary _joinObservers = new(); + + protected readonly Action _onCompleted; + + internal abstract void Match(); + + protected ActivePlan(Action onCompleted) + { + _onCompleted = onCompleted; + } + + protected void AddJoinObserver(IJoinObserver joinObserver) + { + if (!_joinObservers.ContainsKey(joinObserver)) + { + _joinObservers.Add(joinObserver, joinObserver); + } + } + + protected void Dequeue() + { + foreach (var joinObserver in _joinObservers.Values) + { + joinObserver.Dequeue(); + } + } + } + + internal sealed class ActivePlan : ActivePlan + { + private readonly Action _onNext; + private readonly JoinObserver _first; + + internal ActivePlan(JoinObserver first, Action onNext, Action onCompleted) : base(onCompleted) + { + _onNext = onNext; + _first = first; + AddJoinObserver(first); + } + + internal override void Match() + { + if (_first.Queue.Count > 0) + { + var n1 = _first.Queue.Peek(); + + if (n1.Kind == NotificationKind.OnCompleted) + { + _onCompleted(); + } + else + { + Dequeue(); + _onNext(n1.Value); + } + } + } + } + + internal sealed class ActivePlan : ActivePlan + { + private readonly Action _onNext; + private readonly JoinObserver _first; + private readonly JoinObserver _second; + + internal ActivePlan(JoinObserver first, JoinObserver second, Action onNext, Action onCompleted) : base(onCompleted) + { + _onNext = onNext; + _first = first; + _second = second; + AddJoinObserver(first); + AddJoinObserver(second); + } + + internal override void Match() + { + if (_first.Queue.Count > 0 + && _second.Queue.Count > 0) + { + var n1 = _first.Queue.Peek(); + var n2 = _second.Queue.Peek(); + + if (n1.Kind == NotificationKind.OnCompleted + || n2.Kind == NotificationKind.OnCompleted) + { + _onCompleted(); + } + else + { + Dequeue(); + _onNext(n1.Value, + n2.Value + ); + } + } + } + } + + internal sealed class ActivePlan : ActivePlan + { + private readonly Action _onNext; + private readonly JoinObserver _first; + private readonly JoinObserver _second; + private readonly JoinObserver _third; + + internal ActivePlan(JoinObserver first, JoinObserver second, JoinObserver third, Action onNext, Action onCompleted) : base(onCompleted) + { + _onNext = onNext; + _first = first; + _second = second; + _third = third; + AddJoinObserver(first); + AddJoinObserver(second); + AddJoinObserver(third); + } + + internal override void Match() + { + if (_first.Queue.Count > 0 + && _second.Queue.Count > 0 + && _third.Queue.Count > 0) + { + var n1 = _first.Queue.Peek(); + var n2 = _second.Queue.Peek(); + var n3 = _third.Queue.Peek(); + + if (n1.Kind == NotificationKind.OnCompleted + || n2.Kind == NotificationKind.OnCompleted + || n3.Kind == NotificationKind.OnCompleted) + { + _onCompleted(); + } + else + { + Dequeue(); + _onNext(n1.Value, + n2.Value, + n3.Value + ); + } + } + } + } + + internal sealed class ActivePlan : ActivePlan + { + private readonly Action _onNext; + private readonly JoinObserver _first; + private readonly JoinObserver _second; + private readonly JoinObserver _third; + private readonly JoinObserver _fourth; + + internal ActivePlan(JoinObserver first, JoinObserver second, JoinObserver third, JoinObserver fourth, Action onNext, Action onCompleted) : base(onCompleted) + { + _onNext = onNext; + _first = first; + _second = second; + _third = third; + _fourth = fourth; + AddJoinObserver(first); + AddJoinObserver(second); + AddJoinObserver(third); + AddJoinObserver(fourth); + } + + internal override void Match() + { + if (_first.Queue.Count > 0 + && _second.Queue.Count > 0 + && _third.Queue.Count > 0 + && _fourth.Queue.Count > 0) + { + var n1 = _first.Queue.Peek(); + var n2 = _second.Queue.Peek(); + var n3 = _third.Queue.Peek(); + var n4 = _fourth.Queue.Peek(); + + if (n1.Kind == NotificationKind.OnCompleted + || n2.Kind == NotificationKind.OnCompleted + || n3.Kind == NotificationKind.OnCompleted + || n4.Kind == NotificationKind.OnCompleted) + { + _onCompleted(); + } + else + { + Dequeue(); + _onNext(n1.Value, + n2.Value, + n3.Value, + n4.Value + ); + } + } + } + } + + internal sealed class ActivePlan : ActivePlan + { + private readonly Action _onNext; + private readonly JoinObserver _first; + private readonly JoinObserver _second; + private readonly JoinObserver _third; + private readonly JoinObserver _fourth; + private readonly JoinObserver _fifth; + + internal ActivePlan(JoinObserver first, JoinObserver second, JoinObserver third, + JoinObserver fourth, JoinObserver fifth, Action onNext, Action onCompleted) : base(onCompleted) + { + _onNext = onNext; + _first = first; + _second = second; + _third = third; + _fourth = fourth; + _fifth = fifth; + AddJoinObserver(first); + AddJoinObserver(second); + AddJoinObserver(third); + AddJoinObserver(fourth); + AddJoinObserver(fifth); + } + + internal override void Match() + { + if (_first.Queue.Count > 0 + && _second.Queue.Count > 0 + && _third.Queue.Count > 0 + && _fourth.Queue.Count > 0 + && _fifth.Queue.Count > 0) + { + var n1 = _first.Queue.Peek(); + var n2 = _second.Queue.Peek(); + var n3 = _third.Queue.Peek(); + var n4 = _fourth.Queue.Peek(); + var n5 = _fifth.Queue.Peek(); + + if (n1.Kind == NotificationKind.OnCompleted + || n2.Kind == NotificationKind.OnCompleted + || n3.Kind == NotificationKind.OnCompleted + || n4.Kind == NotificationKind.OnCompleted + || n5.Kind == NotificationKind.OnCompleted) + { + _onCompleted(); + } + else + { + Dequeue(); + _onNext(n1.Value, + n2.Value, + n3.Value, + n4.Value, + n5.Value + ); + } + } + } + } + + internal sealed class ActivePlan : ActivePlan + { + private readonly Action _onNext; + private readonly JoinObserver _first; + private readonly JoinObserver _second; + private readonly JoinObserver _third; + private readonly JoinObserver _fourth; + private readonly JoinObserver _fifth; + private readonly JoinObserver _sixth; + + internal ActivePlan(JoinObserver first, JoinObserver second, JoinObserver third, + JoinObserver fourth, JoinObserver fifth, JoinObserver sixth, + Action onNext, Action onCompleted) : base(onCompleted) + { + _onNext = onNext; + _first = first; + _second = second; + _third = third; + _fourth = fourth; + _fifth = fifth; + _sixth = sixth; + AddJoinObserver(first); + AddJoinObserver(second); + AddJoinObserver(third); + AddJoinObserver(fourth); + AddJoinObserver(fifth); + AddJoinObserver(sixth); + } + + internal override void Match() + { + if (_first.Queue.Count > 0 + && _second.Queue.Count > 0 + && _third.Queue.Count > 0 + && _fourth.Queue.Count > 0 + && _fifth.Queue.Count > 0 + && _sixth.Queue.Count > 0) + { + var n1 = _first.Queue.Peek(); + var n2 = _second.Queue.Peek(); + var n3 = _third.Queue.Peek(); + var n4 = _fourth.Queue.Peek(); + var n5 = _fifth.Queue.Peek(); + var n6 = _sixth.Queue.Peek(); + + if (n1.Kind == NotificationKind.OnCompleted + || n2.Kind == NotificationKind.OnCompleted + || n3.Kind == NotificationKind.OnCompleted + || n4.Kind == NotificationKind.OnCompleted + || n5.Kind == NotificationKind.OnCompleted + || n6.Kind == NotificationKind.OnCompleted) + { + _onCompleted(); + } + else + { + Dequeue(); + _onNext(n1.Value, + n2.Value, + n3.Value, + n4.Value, + n5.Value, + n6.Value + ); + } + } + } + } + + internal sealed class ActivePlan : ActivePlan + { + private readonly Action _onNext; + private readonly JoinObserver _first; + private readonly JoinObserver _second; + private readonly JoinObserver _third; + private readonly JoinObserver _fourth; + private readonly JoinObserver _fifth; + private readonly JoinObserver _sixth; + private readonly JoinObserver _seventh; + + internal ActivePlan(JoinObserver first, JoinObserver second, JoinObserver third, + JoinObserver fourth, JoinObserver fifth, JoinObserver sixth, JoinObserver seventh, + Action onNext, Action onCompleted) : base(onCompleted) + { + _onNext = onNext; + _first = first; + _second = second; + _third = third; + _fourth = fourth; + _fifth = fifth; + _sixth = sixth; + _seventh = seventh; + AddJoinObserver(first); + AddJoinObserver(second); + AddJoinObserver(third); + AddJoinObserver(fourth); + AddJoinObserver(fifth); + AddJoinObserver(sixth); + AddJoinObserver(seventh); + } + + internal override void Match() + { + if (_first.Queue.Count > 0 + && _second.Queue.Count > 0 + && _third.Queue.Count > 0 + && _fourth.Queue.Count > 0 + && _fifth.Queue.Count > 0 + && _sixth.Queue.Count > 0 + && _seventh.Queue.Count > 0) + { + var n1 = _first.Queue.Peek(); + var n2 = _second.Queue.Peek(); + var n3 = _third.Queue.Peek(); + var n4 = _fourth.Queue.Peek(); + var n5 = _fifth.Queue.Peek(); + var n6 = _sixth.Queue.Peek(); + var n7 = _seventh.Queue.Peek(); + + if (n1.Kind == NotificationKind.OnCompleted + || n2.Kind == NotificationKind.OnCompleted + || n3.Kind == NotificationKind.OnCompleted + || n4.Kind == NotificationKind.OnCompleted + || n5.Kind == NotificationKind.OnCompleted + || n6.Kind == NotificationKind.OnCompleted + || n7.Kind == NotificationKind.OnCompleted) + { + _onCompleted(); + } + else + { + Dequeue(); + _onNext(n1.Value, + n2.Value, + n3.Value, + n4.Value, + n5.Value, + n6.Value, + n7.Value + ); + } + } + } + } + + internal sealed class ActivePlan : ActivePlan + { + private readonly Action _onNext; + private readonly JoinObserver _first; + private readonly JoinObserver _second; + private readonly JoinObserver _third; + private readonly JoinObserver _fourth; + private readonly JoinObserver _fifth; + private readonly JoinObserver _sixth; + private readonly JoinObserver _seventh; + private readonly JoinObserver _eighth; + + internal ActivePlan(JoinObserver first, JoinObserver second, JoinObserver third, + JoinObserver fourth, JoinObserver fifth, JoinObserver sixth, JoinObserver seventh, + JoinObserver eighth, + Action onNext, Action onCompleted) : base(onCompleted) + { + _onNext = onNext; + _first = first; + _second = second; + _third = third; + _fourth = fourth; + _fifth = fifth; + _sixth = sixth; + _seventh = seventh; + _eighth = eighth; + AddJoinObserver(first); + AddJoinObserver(second); + AddJoinObserver(third); + AddJoinObserver(fourth); + AddJoinObserver(fifth); + AddJoinObserver(sixth); + AddJoinObserver(seventh); + AddJoinObserver(eighth); + } + + internal override void Match() + { + if (_first.Queue.Count > 0 + && _second.Queue.Count > 0 + && _third.Queue.Count > 0 + && _fourth.Queue.Count > 0 + && _fifth.Queue.Count > 0 + && _sixth.Queue.Count > 0 + && _seventh.Queue.Count > 0 + && _eighth.Queue.Count > 0) + { + var n1 = _first.Queue.Peek(); + var n2 = _second.Queue.Peek(); + var n3 = _third.Queue.Peek(); + var n4 = _fourth.Queue.Peek(); + var n5 = _fifth.Queue.Peek(); + var n6 = _sixth.Queue.Peek(); + var n7 = _seventh.Queue.Peek(); + var n8 = _eighth.Queue.Peek(); + + if (n1.Kind == NotificationKind.OnCompleted + || n2.Kind == NotificationKind.OnCompleted + || n3.Kind == NotificationKind.OnCompleted + || n4.Kind == NotificationKind.OnCompleted + || n5.Kind == NotificationKind.OnCompleted + || n6.Kind == NotificationKind.OnCompleted + || n7.Kind == NotificationKind.OnCompleted + || n8.Kind == NotificationKind.OnCompleted + ) + { + _onCompleted(); + } + else + { + Dequeue(); + _onNext(n1.Value, + n2.Value, + n3.Value, + n4.Value, + n5.Value, + n6.Value, + n7.Value, + n8.Value + ); + } + } + } + } + + internal sealed class ActivePlan : ActivePlan + { + private readonly Action _onNext; + private readonly JoinObserver _first; + private readonly JoinObserver _second; + private readonly JoinObserver _third; + private readonly JoinObserver _fourth; + private readonly JoinObserver _fifth; + private readonly JoinObserver _sixth; + private readonly JoinObserver _seventh; + private readonly JoinObserver _eighth; + private readonly JoinObserver _ninth; + + internal ActivePlan(JoinObserver first, JoinObserver second, JoinObserver third, + JoinObserver fourth, JoinObserver fifth, JoinObserver sixth, JoinObserver seventh, + JoinObserver eighth, JoinObserver ninth, + Action onNext, Action onCompleted) : base(onCompleted) + { + _onNext = onNext; + _first = first; + _second = second; + _third = third; + _fourth = fourth; + _fifth = fifth; + _sixth = sixth; + _seventh = seventh; + _eighth = eighth; + _ninth = ninth; + AddJoinObserver(first); + AddJoinObserver(second); + AddJoinObserver(third); + AddJoinObserver(fourth); + AddJoinObserver(fifth); + AddJoinObserver(sixth); + AddJoinObserver(seventh); + AddJoinObserver(eighth); + AddJoinObserver(ninth); + } + + internal override void Match() + { + if (_first.Queue.Count > 0 + && _second.Queue.Count > 0 + && _third.Queue.Count > 0 + && _fourth.Queue.Count > 0 + && _fifth.Queue.Count > 0 + && _sixth.Queue.Count > 0 + && _seventh.Queue.Count > 0 + && _eighth.Queue.Count > 0 + && _ninth.Queue.Count > 0 + ) + { + var n1 = _first.Queue.Peek(); + var n2 = _second.Queue.Peek(); + var n3 = _third.Queue.Peek(); + var n4 = _fourth.Queue.Peek(); + var n5 = _fifth.Queue.Peek(); + var n6 = _sixth.Queue.Peek(); + var n7 = _seventh.Queue.Peek(); + var n8 = _eighth.Queue.Peek(); + var n9 = _ninth.Queue.Peek(); + + if (n1.Kind == NotificationKind.OnCompleted + || n2.Kind == NotificationKind.OnCompleted + || n3.Kind == NotificationKind.OnCompleted + || n4.Kind == NotificationKind.OnCompleted + || n5.Kind == NotificationKind.OnCompleted + || n6.Kind == NotificationKind.OnCompleted + || n7.Kind == NotificationKind.OnCompleted + || n8.Kind == NotificationKind.OnCompleted + || n9.Kind == NotificationKind.OnCompleted + ) + { + _onCompleted(); + } + else + { + Dequeue(); + _onNext(n1.Value, + n2.Value, + n3.Value, + n4.Value, + n5.Value, + n6.Value, + n7.Value, + n8.Value, + n9.Value + ); + } + } + } + } + + internal sealed class ActivePlan : ActivePlan + { + private readonly Action _onNext; + private readonly JoinObserver _first; + private readonly JoinObserver _second; + private readonly JoinObserver _third; + private readonly JoinObserver _fourth; + private readonly JoinObserver _fifth; + private readonly JoinObserver _sixth; + private readonly JoinObserver _seventh; + private readonly JoinObserver _eighth; + private readonly JoinObserver _ninth; + private readonly JoinObserver _tenth; + + internal ActivePlan(JoinObserver first, JoinObserver second, JoinObserver third, + JoinObserver fourth, JoinObserver fifth, JoinObserver sixth, JoinObserver seventh, + JoinObserver eighth, JoinObserver ninth, JoinObserver tenth, + Action onNext, Action onCompleted) : base(onCompleted) + { + _onNext = onNext; + _first = first; + _second = second; + _third = third; + _fourth = fourth; + _fifth = fifth; + _sixth = sixth; + _seventh = seventh; + _eighth = eighth; + _ninth = ninth; + _tenth = tenth; + AddJoinObserver(first); + AddJoinObserver(second); + AddJoinObserver(third); + AddJoinObserver(fourth); + AddJoinObserver(fifth); + AddJoinObserver(sixth); + AddJoinObserver(seventh); + AddJoinObserver(eighth); + AddJoinObserver(ninth); + AddJoinObserver(tenth); + } + + internal override void Match() + { + if (_first.Queue.Count > 0 + && _second.Queue.Count > 0 + && _third.Queue.Count > 0 + && _fourth.Queue.Count > 0 + && _fifth.Queue.Count > 0 + && _sixth.Queue.Count > 0 + && _seventh.Queue.Count > 0 + && _eighth.Queue.Count > 0 + && _ninth.Queue.Count > 0 + && _tenth.Queue.Count > 0 + ) + { + var n1 = _first.Queue.Peek(); + var n2 = _second.Queue.Peek(); + var n3 = _third.Queue.Peek(); + var n4 = _fourth.Queue.Peek(); + var n5 = _fifth.Queue.Peek(); + var n6 = _sixth.Queue.Peek(); + var n7 = _seventh.Queue.Peek(); + var n8 = _eighth.Queue.Peek(); + var n9 = _ninth.Queue.Peek(); + var n10 = _tenth.Queue.Peek(); + + if (n1.Kind == NotificationKind.OnCompleted + || n2.Kind == NotificationKind.OnCompleted + || n3.Kind == NotificationKind.OnCompleted + || n4.Kind == NotificationKind.OnCompleted + || n5.Kind == NotificationKind.OnCompleted + || n6.Kind == NotificationKind.OnCompleted + || n7.Kind == NotificationKind.OnCompleted + || n8.Kind == NotificationKind.OnCompleted + || n9.Kind == NotificationKind.OnCompleted + || n10.Kind == NotificationKind.OnCompleted + ) + { + _onCompleted(); + } + else + { + Dequeue(); + _onNext(n1.Value, + n2.Value, + n3.Value, + n4.Value, + n5.Value, + n6.Value, + n7.Value, + n8.Value, + n9.Value, + n10.Value + ); + } + } + } + } + + internal sealed class ActivePlan : ActivePlan + { + private readonly Action _onNext; + private readonly JoinObserver _first; + private readonly JoinObserver _second; + private readonly JoinObserver _third; + private readonly JoinObserver _fourth; + private readonly JoinObserver _fifth; + private readonly JoinObserver _sixth; + private readonly JoinObserver _seventh; + private readonly JoinObserver _eighth; + private readonly JoinObserver _ninth; + private readonly JoinObserver _tenth; + private readonly JoinObserver _eleventh; + + internal ActivePlan(JoinObserver first, JoinObserver second, JoinObserver third, + JoinObserver fourth, JoinObserver fifth, JoinObserver sixth, JoinObserver seventh, + JoinObserver eighth, JoinObserver ninth, JoinObserver tenth, JoinObserver eleventh, + Action onNext, Action onCompleted) : base(onCompleted) + { + _onNext = onNext; + _first = first; + _second = second; + _third = third; + _fourth = fourth; + _fifth = fifth; + _sixth = sixth; + _seventh = seventh; + _eighth = eighth; + _ninth = ninth; + _tenth = tenth; + _eleventh = eleventh; + AddJoinObserver(first); + AddJoinObserver(second); + AddJoinObserver(third); + AddJoinObserver(fourth); + AddJoinObserver(fifth); + AddJoinObserver(sixth); + AddJoinObserver(seventh); + AddJoinObserver(eighth); + AddJoinObserver(ninth); + AddJoinObserver(tenth); + AddJoinObserver(eleventh); + } + + internal override void Match() + { + if (_first.Queue.Count > 0 + && _second.Queue.Count > 0 + && _third.Queue.Count > 0 + && _fourth.Queue.Count > 0 + && _fifth.Queue.Count > 0 + && _sixth.Queue.Count > 0 + && _seventh.Queue.Count > 0 + && _eighth.Queue.Count > 0 + && _ninth.Queue.Count > 0 + && _tenth.Queue.Count > 0 + && _eleventh.Queue.Count > 0 + ) + { + var n1 = _first.Queue.Peek(); + var n2 = _second.Queue.Peek(); + var n3 = _third.Queue.Peek(); + var n4 = _fourth.Queue.Peek(); + var n5 = _fifth.Queue.Peek(); + var n6 = _sixth.Queue.Peek(); + var n7 = _seventh.Queue.Peek(); + var n8 = _eighth.Queue.Peek(); + var n9 = _ninth.Queue.Peek(); + var n10 = _tenth.Queue.Peek(); + var n11 = _eleventh.Queue.Peek(); + + if (n1.Kind == NotificationKind.OnCompleted + || n2.Kind == NotificationKind.OnCompleted + || n3.Kind == NotificationKind.OnCompleted + || n4.Kind == NotificationKind.OnCompleted + || n5.Kind == NotificationKind.OnCompleted + || n6.Kind == NotificationKind.OnCompleted + || n7.Kind == NotificationKind.OnCompleted + || n8.Kind == NotificationKind.OnCompleted + || n9.Kind == NotificationKind.OnCompleted + || n10.Kind == NotificationKind.OnCompleted + || n11.Kind == NotificationKind.OnCompleted + ) + { + _onCompleted(); + } + else + { + Dequeue(); + _onNext(n1.Value, + n2.Value, + n3.Value, + n4.Value, + n5.Value, + n6.Value, + n7.Value, + n8.Value, + n9.Value, + n10.Value, + n11.Value + ); + } + } + } + } + + internal sealed class ActivePlan : ActivePlan + { + private readonly Action _onNext; + private readonly JoinObserver _first; + private readonly JoinObserver _second; + private readonly JoinObserver _third; + private readonly JoinObserver _fourth; + private readonly JoinObserver _fifth; + private readonly JoinObserver _sixth; + private readonly JoinObserver _seventh; + private readonly JoinObserver _eighth; + private readonly JoinObserver _ninth; + private readonly JoinObserver _tenth; + private readonly JoinObserver _eleventh; + private readonly JoinObserver _twelfth; + + internal ActivePlan(JoinObserver first, JoinObserver second, JoinObserver third, + JoinObserver fourth, JoinObserver fifth, JoinObserver sixth, JoinObserver seventh, + JoinObserver eighth, JoinObserver ninth, JoinObserver tenth, JoinObserver eleventh, + JoinObserver twelfth, + Action onNext, Action onCompleted) : base(onCompleted) + { + _onNext = onNext; + _first = first; + _second = second; + _third = third; + _fourth = fourth; + _fifth = fifth; + _sixth = sixth; + _seventh = seventh; + _eighth = eighth; + _ninth = ninth; + _tenth = tenth; + _eleventh = eleventh; + _twelfth = twelfth; + AddJoinObserver(first); + AddJoinObserver(second); + AddJoinObserver(third); + AddJoinObserver(fourth); + AddJoinObserver(fifth); + AddJoinObserver(sixth); + AddJoinObserver(seventh); + AddJoinObserver(eighth); + AddJoinObserver(ninth); + AddJoinObserver(tenth); + AddJoinObserver(eleventh); + AddJoinObserver(twelfth); + } + + internal override void Match() + { + if (_first.Queue.Count > 0 + && _second.Queue.Count > 0 + && _third.Queue.Count > 0 + && _fourth.Queue.Count > 0 + && _fifth.Queue.Count > 0 + && _sixth.Queue.Count > 0 + && _seventh.Queue.Count > 0 + && _eighth.Queue.Count > 0 + && _ninth.Queue.Count > 0 + && _tenth.Queue.Count > 0 + && _eleventh.Queue.Count > 0 + && _twelfth.Queue.Count > 0 + ) + { + var n1 = _first.Queue.Peek(); + var n2 = _second.Queue.Peek(); + var n3 = _third.Queue.Peek(); + var n4 = _fourth.Queue.Peek(); + var n5 = _fifth.Queue.Peek(); + var n6 = _sixth.Queue.Peek(); + var n7 = _seventh.Queue.Peek(); + var n8 = _eighth.Queue.Peek(); + var n9 = _ninth.Queue.Peek(); + var n10 = _tenth.Queue.Peek(); + var n11 = _eleventh.Queue.Peek(); + var n12 = _twelfth.Queue.Peek(); + + if (n1.Kind == NotificationKind.OnCompleted + || n2.Kind == NotificationKind.OnCompleted + || n3.Kind == NotificationKind.OnCompleted + || n4.Kind == NotificationKind.OnCompleted + || n5.Kind == NotificationKind.OnCompleted + || n6.Kind == NotificationKind.OnCompleted + || n7.Kind == NotificationKind.OnCompleted + || n8.Kind == NotificationKind.OnCompleted + || n9.Kind == NotificationKind.OnCompleted + || n10.Kind == NotificationKind.OnCompleted + || n11.Kind == NotificationKind.OnCompleted + || n12.Kind == NotificationKind.OnCompleted + ) + { + _onCompleted(); + } + else + { + Dequeue(); + _onNext(n1.Value, + n2.Value, + n3.Value, + n4.Value, + n5.Value, + n6.Value, + n7.Value, + n8.Value, + n9.Value, + n10.Value, + n11.Value, + n12.Value + ); + } + } + } + } + + internal sealed class ActivePlan : ActivePlan + { + private readonly Action _onNext; + private readonly JoinObserver _first; + private readonly JoinObserver _second; + private readonly JoinObserver _third; + private readonly JoinObserver _fourth; + private readonly JoinObserver _fifth; + private readonly JoinObserver _sixth; + private readonly JoinObserver _seventh; + private readonly JoinObserver _eighth; + private readonly JoinObserver _ninth; + private readonly JoinObserver _tenth; + private readonly JoinObserver _eleventh; + private readonly JoinObserver _twelfth; + private readonly JoinObserver _thirteenth; + + internal ActivePlan(JoinObserver first, JoinObserver second, JoinObserver third, + JoinObserver fourth, JoinObserver fifth, JoinObserver sixth, JoinObserver seventh, + JoinObserver eighth, JoinObserver ninth, JoinObserver tenth, JoinObserver eleventh, + JoinObserver twelfth, JoinObserver thirteenth, + Action onNext, Action onCompleted) : base(onCompleted) + { + _onNext = onNext; + _first = first; + _second = second; + _third = third; + _fourth = fourth; + _fifth = fifth; + _sixth = sixth; + _seventh = seventh; + _eighth = eighth; + _ninth = ninth; + _tenth = tenth; + _eleventh = eleventh; + _twelfth = twelfth; + _thirteenth = thirteenth; + AddJoinObserver(first); + AddJoinObserver(second); + AddJoinObserver(third); + AddJoinObserver(fourth); + AddJoinObserver(fifth); + AddJoinObserver(sixth); + AddJoinObserver(seventh); + AddJoinObserver(eighth); + AddJoinObserver(ninth); + AddJoinObserver(tenth); + AddJoinObserver(eleventh); + AddJoinObserver(twelfth); + AddJoinObserver(thirteenth); + } + + internal override void Match() + { + if (_first.Queue.Count > 0 + && _second.Queue.Count > 0 + && _third.Queue.Count > 0 + && _fourth.Queue.Count > 0 + && _fifth.Queue.Count > 0 + && _sixth.Queue.Count > 0 + && _seventh.Queue.Count > 0 + && _eighth.Queue.Count > 0 + && _ninth.Queue.Count > 0 + && _tenth.Queue.Count > 0 + && _eleventh.Queue.Count > 0 + && _twelfth.Queue.Count > 0 + && _thirteenth.Queue.Count > 0 + ) + { + var n1 = _first.Queue.Peek(); + var n2 = _second.Queue.Peek(); + var n3 = _third.Queue.Peek(); + var n4 = _fourth.Queue.Peek(); + var n5 = _fifth.Queue.Peek(); + var n6 = _sixth.Queue.Peek(); + var n7 = _seventh.Queue.Peek(); + var n8 = _eighth.Queue.Peek(); + var n9 = _ninth.Queue.Peek(); + var n10 = _tenth.Queue.Peek(); + var n11 = _eleventh.Queue.Peek(); + var n12 = _twelfth.Queue.Peek(); + var n13 = _thirteenth.Queue.Peek(); + + if (n1.Kind == NotificationKind.OnCompleted + || n2.Kind == NotificationKind.OnCompleted + || n3.Kind == NotificationKind.OnCompleted + || n4.Kind == NotificationKind.OnCompleted + || n5.Kind == NotificationKind.OnCompleted + || n6.Kind == NotificationKind.OnCompleted + || n7.Kind == NotificationKind.OnCompleted + || n8.Kind == NotificationKind.OnCompleted + || n9.Kind == NotificationKind.OnCompleted + || n10.Kind == NotificationKind.OnCompleted + || n11.Kind == NotificationKind.OnCompleted + || n12.Kind == NotificationKind.OnCompleted + || n13.Kind == NotificationKind.OnCompleted + ) + { + _onCompleted(); + } + else + { + Dequeue(); + _onNext(n1.Value, + n2.Value, + n3.Value, + n4.Value, + n5.Value, + n6.Value, + n7.Value, + n8.Value, + n9.Value, + n10.Value, + n11.Value, + n12.Value, + n13.Value + ); + } + } + } + } + + internal sealed class ActivePlan : ActivePlan + { + private readonly Action _onNext; + private readonly JoinObserver _first; + private readonly JoinObserver _second; + private readonly JoinObserver _third; + private readonly JoinObserver _fourth; + private readonly JoinObserver _fifth; + private readonly JoinObserver _sixth; + private readonly JoinObserver _seventh; + private readonly JoinObserver _eighth; + private readonly JoinObserver _ninth; + private readonly JoinObserver _tenth; + private readonly JoinObserver _eleventh; + private readonly JoinObserver _twelfth; + private readonly JoinObserver _thirteenth; + private readonly JoinObserver _fourteenth; + + internal ActivePlan(JoinObserver first, JoinObserver second, JoinObserver third, + JoinObserver fourth, JoinObserver fifth, JoinObserver sixth, JoinObserver seventh, + JoinObserver eighth, JoinObserver ninth, JoinObserver tenth, JoinObserver eleventh, + JoinObserver twelfth, JoinObserver thirteenth, JoinObserver fourteenth, + Action onNext, Action onCompleted) : base(onCompleted) + { + _onNext = onNext; + _first = first; + _second = second; + _third = third; + _fourth = fourth; + _fifth = fifth; + _sixth = sixth; + _seventh = seventh; + _eighth = eighth; + _ninth = ninth; + _tenth = tenth; + _eleventh = eleventh; + _twelfth = twelfth; + _thirteenth = thirteenth; + _fourteenth = fourteenth; + AddJoinObserver(first); + AddJoinObserver(second); + AddJoinObserver(third); + AddJoinObserver(fourth); + AddJoinObserver(fifth); + AddJoinObserver(sixth); + AddJoinObserver(seventh); + AddJoinObserver(eighth); + AddJoinObserver(ninth); + AddJoinObserver(tenth); + AddJoinObserver(eleventh); + AddJoinObserver(twelfth); + AddJoinObserver(thirteenth); + AddJoinObserver(fourteenth); + } + + internal override void Match() + { + if (_first.Queue.Count > 0 + && _second.Queue.Count > 0 + && _third.Queue.Count > 0 + && _fourth.Queue.Count > 0 + && _fifth.Queue.Count > 0 + && _sixth.Queue.Count > 0 + && _seventh.Queue.Count > 0 + && _eighth.Queue.Count > 0 + && _ninth.Queue.Count > 0 + && _tenth.Queue.Count > 0 + && _eleventh.Queue.Count > 0 + && _twelfth.Queue.Count > 0 + && _thirteenth.Queue.Count > 0 + && _fourteenth.Queue.Count > 0 + ) + { + var n1 = _first.Queue.Peek(); + var n2 = _second.Queue.Peek(); + var n3 = _third.Queue.Peek(); + var n4 = _fourth.Queue.Peek(); + var n5 = _fifth.Queue.Peek(); + var n6 = _sixth.Queue.Peek(); + var n7 = _seventh.Queue.Peek(); + var n8 = _eighth.Queue.Peek(); + var n9 = _ninth.Queue.Peek(); + var n10 = _tenth.Queue.Peek(); + var n11 = _eleventh.Queue.Peek(); + var n12 = _twelfth.Queue.Peek(); + var n13 = _thirteenth.Queue.Peek(); + var n14 = _fourteenth.Queue.Peek(); + + if (n1.Kind == NotificationKind.OnCompleted + || n2.Kind == NotificationKind.OnCompleted + || n3.Kind == NotificationKind.OnCompleted + || n4.Kind == NotificationKind.OnCompleted + || n5.Kind == NotificationKind.OnCompleted + || n6.Kind == NotificationKind.OnCompleted + || n7.Kind == NotificationKind.OnCompleted + || n8.Kind == NotificationKind.OnCompleted + || n9.Kind == NotificationKind.OnCompleted + || n10.Kind == NotificationKind.OnCompleted + || n11.Kind == NotificationKind.OnCompleted + || n12.Kind == NotificationKind.OnCompleted + || n13.Kind == NotificationKind.OnCompleted + || n14.Kind == NotificationKind.OnCompleted + ) + { + _onCompleted(); + } + else + { + Dequeue(); + _onNext(n1.Value, + n2.Value, + n3.Value, + n4.Value, + n5.Value, + n6.Value, + n7.Value, + n8.Value, + n9.Value, + n10.Value, + n11.Value, + n12.Value, + n13.Value, + n14.Value + ); + } + } + } + } + + internal sealed class ActivePlan : ActivePlan + { + private readonly Action _onNext; + private readonly JoinObserver _first; + private readonly JoinObserver _second; + private readonly JoinObserver _third; + private readonly JoinObserver _fourth; + private readonly JoinObserver _fifth; + private readonly JoinObserver _sixth; + private readonly JoinObserver _seventh; + private readonly JoinObserver _eighth; + private readonly JoinObserver _ninth; + private readonly JoinObserver _tenth; + private readonly JoinObserver _eleventh; + private readonly JoinObserver _twelfth; + private readonly JoinObserver _thirteenth; + private readonly JoinObserver _fourteenth; + private readonly JoinObserver _fifteenth; + + internal ActivePlan(JoinObserver first, JoinObserver second, JoinObserver third, + JoinObserver fourth, JoinObserver fifth, JoinObserver sixth, JoinObserver seventh, + JoinObserver eighth, JoinObserver ninth, JoinObserver tenth, JoinObserver eleventh, + JoinObserver twelfth, JoinObserver thirteenth, JoinObserver fourteenth, + JoinObserver fifteenth, + Action onNext, Action onCompleted) : base(onCompleted) + { + _onNext = onNext; + _first = first; + _second = second; + _third = third; + _fourth = fourth; + _fifth = fifth; + _sixth = sixth; + _seventh = seventh; + _eighth = eighth; + _ninth = ninth; + _tenth = tenth; + _eleventh = eleventh; + _twelfth = twelfth; + _thirteenth = thirteenth; + _fourteenth = fourteenth; + _fifteenth = fifteenth; + AddJoinObserver(first); + AddJoinObserver(second); + AddJoinObserver(third); + AddJoinObserver(fourth); + AddJoinObserver(fifth); + AddJoinObserver(sixth); + AddJoinObserver(seventh); + AddJoinObserver(eighth); + AddJoinObserver(ninth); + AddJoinObserver(tenth); + AddJoinObserver(eleventh); + AddJoinObserver(twelfth); + AddJoinObserver(thirteenth); + AddJoinObserver(fourteenth); + AddJoinObserver(fifteenth); + } + + internal override void Match() + { + if (_first.Queue.Count > 0 + && _second.Queue.Count > 0 + && _third.Queue.Count > 0 + && _fourth.Queue.Count > 0 + && _fifth.Queue.Count > 0 + && _sixth.Queue.Count > 0 + && _seventh.Queue.Count > 0 + && _eighth.Queue.Count > 0 + && _ninth.Queue.Count > 0 + && _tenth.Queue.Count > 0 + && _eleventh.Queue.Count > 0 + && _twelfth.Queue.Count > 0 + && _thirteenth.Queue.Count > 0 + && _fourteenth.Queue.Count > 0 + && _fifteenth.Queue.Count > 0 + ) + { + var n1 = _first.Queue.Peek(); + var n2 = _second.Queue.Peek(); + var n3 = _third.Queue.Peek(); + var n4 = _fourth.Queue.Peek(); + var n5 = _fifth.Queue.Peek(); + var n6 = _sixth.Queue.Peek(); + var n7 = _seventh.Queue.Peek(); + var n8 = _eighth.Queue.Peek(); + var n9 = _ninth.Queue.Peek(); + var n10 = _tenth.Queue.Peek(); + var n11 = _eleventh.Queue.Peek(); + var n12 = _twelfth.Queue.Peek(); + var n13 = _thirteenth.Queue.Peek(); + var n14 = _fourteenth.Queue.Peek(); + var n15 = _fifteenth.Queue.Peek(); + + if (n1.Kind == NotificationKind.OnCompleted + || n2.Kind == NotificationKind.OnCompleted + || n3.Kind == NotificationKind.OnCompleted + || n4.Kind == NotificationKind.OnCompleted + || n5.Kind == NotificationKind.OnCompleted + || n6.Kind == NotificationKind.OnCompleted + || n7.Kind == NotificationKind.OnCompleted + || n8.Kind == NotificationKind.OnCompleted + || n9.Kind == NotificationKind.OnCompleted + || n10.Kind == NotificationKind.OnCompleted + || n11.Kind == NotificationKind.OnCompleted + || n12.Kind == NotificationKind.OnCompleted + || n13.Kind == NotificationKind.OnCompleted + || n14.Kind == NotificationKind.OnCompleted + || n15.Kind == NotificationKind.OnCompleted + ) + { + _onCompleted(); + } + else + { + Dequeue(); + _onNext(n1.Value, + n2.Value, + n3.Value, + n4.Value, + n5.Value, + n6.Value, + n7.Value, + n8.Value, + n9.Value, + n10.Value, + n11.Value, + n12.Value, + n13.Value, + n14.Value, + n15.Value + ); + } + } + } + } + + internal sealed class ActivePlan : ActivePlan + { + private readonly Action _onNext; + private readonly JoinObserver _first; + private readonly JoinObserver _second; + private readonly JoinObserver _third; + private readonly JoinObserver _fourth; + private readonly JoinObserver _fifth; + private readonly JoinObserver _sixth; + private readonly JoinObserver _seventh; + private readonly JoinObserver _eighth; + private readonly JoinObserver _ninth; + private readonly JoinObserver _tenth; + private readonly JoinObserver _eleventh; + private readonly JoinObserver _twelfth; + private readonly JoinObserver _thirteenth; + private readonly JoinObserver _fourteenth; + private readonly JoinObserver _fifteenth; + private readonly JoinObserver _sixteenth; + + internal ActivePlan(JoinObserver first, JoinObserver second, JoinObserver third, + JoinObserver fourth, JoinObserver fifth, JoinObserver sixth, JoinObserver seventh, + JoinObserver eighth, JoinObserver ninth, JoinObserver tenth, JoinObserver eleventh, + JoinObserver twelfth, JoinObserver thirteenth, JoinObserver fourteenth, + JoinObserver fifteenth, JoinObserver sixteenth, + Action onNext, Action onCompleted) : base(onCompleted) + { + _onNext = onNext; + _first = first; + _second = second; + _third = third; + _fourth = fourth; + _fifth = fifth; + _sixth = sixth; + _seventh = seventh; + _eighth = eighth; + _ninth = ninth; + _tenth = tenth; + _eleventh = eleventh; + _twelfth = twelfth; + _thirteenth = thirteenth; + _fourteenth = fourteenth; + _fifteenth = fifteenth; + _sixteenth = sixteenth; + AddJoinObserver(first); + AddJoinObserver(second); + AddJoinObserver(third); + AddJoinObserver(fourth); + AddJoinObserver(fifth); + AddJoinObserver(sixth); + AddJoinObserver(seventh); + AddJoinObserver(eighth); + AddJoinObserver(ninth); + AddJoinObserver(tenth); + AddJoinObserver(eleventh); + AddJoinObserver(twelfth); + AddJoinObserver(thirteenth); + AddJoinObserver(fourteenth); + AddJoinObserver(fifteenth); + AddJoinObserver(sixteenth); + } + + internal override void Match() + { + if (_first.Queue.Count > 0 + && _second.Queue.Count > 0 + && _third.Queue.Count > 0 + && _fourth.Queue.Count > 0 + && _fifth.Queue.Count > 0 + && _sixth.Queue.Count > 0 + && _seventh.Queue.Count > 0 + && _eighth.Queue.Count > 0 + && _ninth.Queue.Count > 0 + && _tenth.Queue.Count > 0 + && _eleventh.Queue.Count > 0 + && _twelfth.Queue.Count > 0 + && _thirteenth.Queue.Count > 0 + && _fourteenth.Queue.Count > 0 + && _fifteenth.Queue.Count > 0 + && _sixteenth.Queue.Count > 0 + ) + { + var n1 = _first.Queue.Peek(); + var n2 = _second.Queue.Peek(); + var n3 = _third.Queue.Peek(); + var n4 = _fourth.Queue.Peek(); + var n5 = _fifth.Queue.Peek(); + var n6 = _sixth.Queue.Peek(); + var n7 = _seventh.Queue.Peek(); + var n8 = _eighth.Queue.Peek(); + var n9 = _ninth.Queue.Peek(); + var n10 = _tenth.Queue.Peek(); + var n11 = _eleventh.Queue.Peek(); + var n12 = _twelfth.Queue.Peek(); + var n13 = _thirteenth.Queue.Peek(); + var n14 = _fourteenth.Queue.Peek(); + var n15 = _fifteenth.Queue.Peek(); + var n16 = _sixteenth.Queue.Peek(); + + if (n1.Kind == NotificationKind.OnCompleted + || n2.Kind == NotificationKind.OnCompleted + || n3.Kind == NotificationKind.OnCompleted + || n4.Kind == NotificationKind.OnCompleted + || n5.Kind == NotificationKind.OnCompleted + || n6.Kind == NotificationKind.OnCompleted + || n7.Kind == NotificationKind.OnCompleted + || n8.Kind == NotificationKind.OnCompleted + || n9.Kind == NotificationKind.OnCompleted + || n10.Kind == NotificationKind.OnCompleted + || n11.Kind == NotificationKind.OnCompleted + || n12.Kind == NotificationKind.OnCompleted + || n13.Kind == NotificationKind.OnCompleted + || n14.Kind == NotificationKind.OnCompleted + || n15.Kind == NotificationKind.OnCompleted + || n16.Kind == NotificationKind.OnCompleted + ) + { + _onCompleted(); + } + else + { + Dequeue(); + _onNext(n1.Value, + n2.Value, + n3.Value, + n4.Value, + n5.Value, + n6.Value, + n7.Value, + n8.Value, + n9.Value, + n10.Value, + n11.Value, + n12.Value, + n13.Value, + n14.Value, + n15.Value, + n16.Value + ); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Joins/JoinObserver.cs b/LibExternal/System.Reactive/Joins/JoinObserver.cs new file mode 100644 index 0000000..23f92b4 --- /dev/null +++ b/LibExternal/System.Reactive/Joins/JoinObserver.cs @@ -0,0 +1,105 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Disposables; +using System.Reactive.Linq; + +namespace System.Reactive.Joins +{ + internal interface IJoinObserver : IDisposable + { + void Subscribe(object gate); + void Dequeue(); + } + + internal sealed class JoinObserver : ObserverBase>, IJoinObserver + { + private object? _gate; + private readonly IObservable _source; + private readonly Action _onError; + private readonly List _activePlans; + private SingleAssignmentDisposableValue _subscription; + private bool _isDisposed; + + public JoinObserver(IObservable source, Action onError) + { + _source = source; + _onError = onError; + Queue = new Queue>(); + _activePlans = new List(); + } + + public Queue> Queue { get; } + + public void AddActivePlan(ActivePlan activePlan) + { + _activePlans.Add(activePlan); + } + + public void Subscribe(object gate) + { + _gate = gate; + _subscription.Disposable = _source.Materialize().SubscribeSafe(this); + } + + public void Dequeue() + { + Queue.Dequeue(); + } + + protected override void OnNextCore(Notification notification) + { + lock (_gate!) // NB: Called after Subscribe(object) is called. + { + if (!_isDisposed) + { + if (notification.Kind == NotificationKind.OnError) + { + _onError(notification.Exception!); + return; + } + + Queue.Enqueue(notification); + foreach (var activePlan in _activePlans.ToArray()) // Working on a copy since _activePlans might change while iterating. + { + activePlan.Match(); + } + } + } + } + + protected override void OnErrorCore(Exception exception) + { + } + + protected override void OnCompletedCore() + { + } + + internal void RemoveActivePlan(ActivePlan activePlan) + { + _activePlans.Remove(activePlan); + if (_activePlans.Count == 0) + { + Dispose(); + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (!_isDisposed) + { + if (disposing) + { + _subscription.Dispose(); + } + + _isDisposed = true; + } + } + } +} diff --git a/LibExternal/System.Reactive/Joins/Pattern.Generated.cs b/LibExternal/System.Reactive/Joins/Pattern.Generated.cs new file mode 100644 index 0000000..e35d84b --- /dev/null +++ b/LibExternal/System.Reactive/Joins/Pattern.Generated.cs @@ -0,0 +1,1106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Joins +{ + /* The following code is generated by a T4 template. */ + + #region Joins auto-generated code (10/01/2020 12:41:25) + + /// + /// Represents a join pattern over one observable sequence. + /// + /// The type of the elements in the first source sequence. + public class Pattern : Pattern + { + internal Pattern(IObservable first) + { + First = first; + } + + internal IObservable First { get; } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public Plan Then(Func selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return new Plan(this, selector); + } + } + + /// + /// Represents a join pattern over two observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + public class Pattern : Pattern + { + internal Pattern(IObservable first, IObservable second) + { + First = first; + Second = second; + } + + internal IObservable First { get; } + internal IObservable Second { get; } + + /// + /// Creates a pattern that matches when all three observable sequences have an available element. + /// + /// The type of the elements in the third observable sequence. + /// Observable sequence to match with the two previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public Pattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return new Pattern(First, Second, other); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public Plan Then(Func selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return new Plan(this, selector); + } + } + + /// + /// Represents a join pattern over three observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + public class Pattern : Pattern + { + internal Pattern(IObservable first, IObservable second, IObservable third) + { + First = first; + Second = second; + Third = third; + } + + internal IObservable First { get; } + internal IObservable Second { get; } + internal IObservable Third { get; } + + /// + /// Creates a pattern that matches when all four observable sequences have an available element. + /// + /// The type of the elements in the fourth observable sequence. + /// Observable sequence to match with the three previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public Pattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return new Pattern(First, Second, Third, other); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public Plan Then(Func selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return new Plan(this, selector); + } + } + + /// + /// Represents a join pattern over four observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + public class Pattern : Pattern + { + internal Pattern(IObservable first, IObservable second, IObservable third, IObservable fourth) + { + First = first; + Second = second; + Third = third; + Fourth = fourth; + } + + internal IObservable First { get; } + internal IObservable Second { get; } + internal IObservable Third { get; } + internal IObservable Fourth { get; } + + /// + /// Creates a pattern that matches when all five observable sequences have an available element. + /// + /// The type of the elements in the fifth observable sequence. + /// Observable sequence to match with the four previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public Pattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return new Pattern(First, Second, Third, Fourth, other); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public Plan Then(Func selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return new Plan(this, selector); + } + } + + /// + /// Represents a join pattern over five observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + public class Pattern : Pattern + { + internal Pattern(IObservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth) + { + First = first; + Second = second; + Third = third; + Fourth = fourth; + Fifth = fifth; + } + + internal IObservable First { get; } + internal IObservable Second { get; } + internal IObservable Third { get; } + internal IObservable Fourth { get; } + internal IObservable Fifth { get; } + + /// + /// Creates a pattern that matches when all six observable sequences have an available element. + /// + /// The type of the elements in the sixth observable sequence. + /// Observable sequence to match with the five previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public Pattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return new Pattern(First, Second, Third, Fourth, Fifth, other); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public Plan Then(Func selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return new Plan(this, selector); + } + } + + /// + /// Represents a join pattern over six observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + public class Pattern : Pattern + { + internal Pattern(IObservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth) + { + First = first; + Second = second; + Third = third; + Fourth = fourth; + Fifth = fifth; + Sixth = sixth; + } + + internal IObservable First { get; } + internal IObservable Second { get; } + internal IObservable Third { get; } + internal IObservable Fourth { get; } + internal IObservable Fifth { get; } + internal IObservable Sixth { get; } + + /// + /// Creates a pattern that matches when all seven observable sequences have an available element. + /// + /// The type of the elements in the seventh observable sequence. + /// Observable sequence to match with the six previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public Pattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return new Pattern(First, Second, Third, Fourth, Fifth, Sixth, other); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public Plan Then(Func selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return new Plan(this, selector); + } + } + + /// + /// Represents a join pattern over seven observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + public class Pattern : Pattern + { + internal Pattern(IObservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh) + { + First = first; + Second = second; + Third = third; + Fourth = fourth; + Fifth = fifth; + Sixth = sixth; + Seventh = seventh; + } + + internal IObservable First { get; } + internal IObservable Second { get; } + internal IObservable Third { get; } + internal IObservable Fourth { get; } + internal IObservable Fifth { get; } + internal IObservable Sixth { get; } + internal IObservable Seventh { get; } + + /// + /// Creates a pattern that matches when all eight observable sequences have an available element. + /// + /// The type of the elements in the eighth observable sequence. + /// Observable sequence to match with the seven previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public Pattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return new Pattern(First, Second, Third, Fourth, Fifth, Sixth, Seventh, other); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public Plan Then(Func selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return new Plan(this, selector); + } + } + + /// + /// Represents a join pattern over eight observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + public class Pattern : Pattern + { + internal Pattern(IObservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth) + { + First = first; + Second = second; + Third = third; + Fourth = fourth; + Fifth = fifth; + Sixth = sixth; + Seventh = seventh; + Eighth = eighth; + } + + internal IObservable First { get; } + internal IObservable Second { get; } + internal IObservable Third { get; } + internal IObservable Fourth { get; } + internal IObservable Fifth { get; } + internal IObservable Sixth { get; } + internal IObservable Seventh { get; } + internal IObservable Eighth { get; } + + /// + /// Creates a pattern that matches when all nine observable sequences have an available element. + /// + /// The type of the elements in the ninth observable sequence. + /// Observable sequence to match with the eight previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public Pattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return new Pattern(First, Second, Third, Fourth, Fifth, Sixth, Seventh, Eighth, other); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public Plan Then(Func selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return new Plan(this, selector); + } + } + + /// + /// Represents a join pattern over nine observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + public class Pattern : Pattern + { + internal Pattern(IObservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth, IObservable ninth) + { + First = first; + Second = second; + Third = third; + Fourth = fourth; + Fifth = fifth; + Sixth = sixth; + Seventh = seventh; + Eighth = eighth; + Ninth = ninth; + } + + internal IObservable First { get; } + internal IObservable Second { get; } + internal IObservable Third { get; } + internal IObservable Fourth { get; } + internal IObservable Fifth { get; } + internal IObservable Sixth { get; } + internal IObservable Seventh { get; } + internal IObservable Eighth { get; } + internal IObservable Ninth { get; } + + /// + /// Creates a pattern that matches when all ten observable sequences have an available element. + /// + /// The type of the elements in the tenth observable sequence. + /// Observable sequence to match with the nine previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public Pattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return new Pattern(First, Second, Third, Fourth, Fifth, Sixth, Seventh, Eighth, Ninth, other); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public Plan Then(Func selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return new Plan(this, selector); + } + } + + /// + /// Represents a join pattern over ten observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + public class Pattern : Pattern + { + internal Pattern(IObservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth, IObservable ninth, IObservable tenth) + { + First = first; + Second = second; + Third = third; + Fourth = fourth; + Fifth = fifth; + Sixth = sixth; + Seventh = seventh; + Eighth = eighth; + Ninth = ninth; + Tenth = tenth; + } + + internal IObservable First { get; } + internal IObservable Second { get; } + internal IObservable Third { get; } + internal IObservable Fourth { get; } + internal IObservable Fifth { get; } + internal IObservable Sixth { get; } + internal IObservable Seventh { get; } + internal IObservable Eighth { get; } + internal IObservable Ninth { get; } + internal IObservable Tenth { get; } + + /// + /// Creates a pattern that matches when all eleven observable sequences have an available element. + /// + /// The type of the elements in the eleventh observable sequence. + /// Observable sequence to match with the ten previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public Pattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return new Pattern(First, Second, Third, Fourth, Fifth, Sixth, Seventh, Eighth, Ninth, Tenth, other); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public Plan Then(Func selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return new Plan(this, selector); + } + } + + /// + /// Represents a join pattern over eleven observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + public class Pattern : Pattern + { + internal Pattern(IObservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth, IObservable ninth, IObservable tenth, IObservable eleventh) + { + First = first; + Second = second; + Third = third; + Fourth = fourth; + Fifth = fifth; + Sixth = sixth; + Seventh = seventh; + Eighth = eighth; + Ninth = ninth; + Tenth = tenth; + Eleventh = eleventh; + } + + internal IObservable First { get; } + internal IObservable Second { get; } + internal IObservable Third { get; } + internal IObservable Fourth { get; } + internal IObservable Fifth { get; } + internal IObservable Sixth { get; } + internal IObservable Seventh { get; } + internal IObservable Eighth { get; } + internal IObservable Ninth { get; } + internal IObservable Tenth { get; } + internal IObservable Eleventh { get; } + + /// + /// Creates a pattern that matches when all twelve observable sequences have an available element. + /// + /// The type of the elements in the twelfth observable sequence. + /// Observable sequence to match with the eleven previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public Pattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return new Pattern(First, Second, Third, Fourth, Fifth, Sixth, Seventh, Eighth, Ninth, Tenth, Eleventh, other); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public Plan Then(Func selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return new Plan(this, selector); + } + } + + /// + /// Represents a join pattern over twelve observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + public class Pattern : Pattern + { + internal Pattern(IObservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth, IObservable ninth, IObservable tenth, IObservable eleventh, IObservable twelfth) + { + First = first; + Second = second; + Third = third; + Fourth = fourth; + Fifth = fifth; + Sixth = sixth; + Seventh = seventh; + Eighth = eighth; + Ninth = ninth; + Tenth = tenth; + Eleventh = eleventh; + Twelfth = twelfth; + } + + internal IObservable First { get; } + internal IObservable Second { get; } + internal IObservable Third { get; } + internal IObservable Fourth { get; } + internal IObservable Fifth { get; } + internal IObservable Sixth { get; } + internal IObservable Seventh { get; } + internal IObservable Eighth { get; } + internal IObservable Ninth { get; } + internal IObservable Tenth { get; } + internal IObservable Eleventh { get; } + internal IObservable Twelfth { get; } + + /// + /// Creates a pattern that matches when all thirteen observable sequences have an available element. + /// + /// The type of the elements in the thirteenth observable sequence. + /// Observable sequence to match with the twelve previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public Pattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return new Pattern(First, Second, Third, Fourth, Fifth, Sixth, Seventh, Eighth, Ninth, Tenth, Eleventh, Twelfth, other); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public Plan Then(Func selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return new Plan(this, selector); + } + } + + /// + /// Represents a join pattern over thirteen observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + public class Pattern : Pattern + { + internal Pattern(IObservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth, IObservable ninth, IObservable tenth, IObservable eleventh, IObservable twelfth, IObservable thirteenth) + { + First = first; + Second = second; + Third = third; + Fourth = fourth; + Fifth = fifth; + Sixth = sixth; + Seventh = seventh; + Eighth = eighth; + Ninth = ninth; + Tenth = tenth; + Eleventh = eleventh; + Twelfth = twelfth; + Thirteenth = thirteenth; + } + + internal IObservable First { get; } + internal IObservable Second { get; } + internal IObservable Third { get; } + internal IObservable Fourth { get; } + internal IObservable Fifth { get; } + internal IObservable Sixth { get; } + internal IObservable Seventh { get; } + internal IObservable Eighth { get; } + internal IObservable Ninth { get; } + internal IObservable Tenth { get; } + internal IObservable Eleventh { get; } + internal IObservable Twelfth { get; } + internal IObservable Thirteenth { get; } + + /// + /// Creates a pattern that matches when all fourteen observable sequences have an available element. + /// + /// The type of the elements in the fourteenth observable sequence. + /// Observable sequence to match with the thirteen previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public Pattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return new Pattern(First, Second, Third, Fourth, Fifth, Sixth, Seventh, Eighth, Ninth, Tenth, Eleventh, Twelfth, Thirteenth, other); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public Plan Then(Func selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return new Plan(this, selector); + } + } + + /// + /// Represents a join pattern over fourteen observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the fourteenth source sequence. + public class Pattern : Pattern + { + internal Pattern(IObservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth, IObservable ninth, IObservable tenth, IObservable eleventh, IObservable twelfth, IObservable thirteenth, IObservable fourteenth) + { + First = first; + Second = second; + Third = third; + Fourth = fourth; + Fifth = fifth; + Sixth = sixth; + Seventh = seventh; + Eighth = eighth; + Ninth = ninth; + Tenth = tenth; + Eleventh = eleventh; + Twelfth = twelfth; + Thirteenth = thirteenth; + Fourteenth = fourteenth; + } + + internal IObservable First { get; } + internal IObservable Second { get; } + internal IObservable Third { get; } + internal IObservable Fourth { get; } + internal IObservable Fifth { get; } + internal IObservable Sixth { get; } + internal IObservable Seventh { get; } + internal IObservable Eighth { get; } + internal IObservable Ninth { get; } + internal IObservable Tenth { get; } + internal IObservable Eleventh { get; } + internal IObservable Twelfth { get; } + internal IObservable Thirteenth { get; } + internal IObservable Fourteenth { get; } + + /// + /// Creates a pattern that matches when all fifteen observable sequences have an available element. + /// + /// The type of the elements in the fifteenth observable sequence. + /// Observable sequence to match with the fourteen previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public Pattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return new Pattern(First, Second, Third, Fourth, Fifth, Sixth, Seventh, Eighth, Ninth, Tenth, Eleventh, Twelfth, Thirteenth, Fourteenth, other); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public Plan Then(Func selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return new Plan(this, selector); + } + } + + /// + /// Represents a join pattern over fifteen observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the fourteenth source sequence. + /// The type of the elements in the fifteenth source sequence. + public class Pattern : Pattern + { + internal Pattern(IObservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth, IObservable ninth, IObservable tenth, IObservable eleventh, IObservable twelfth, IObservable thirteenth, IObservable fourteenth, IObservable fifteenth) + { + First = first; + Second = second; + Third = third; + Fourth = fourth; + Fifth = fifth; + Sixth = sixth; + Seventh = seventh; + Eighth = eighth; + Ninth = ninth; + Tenth = tenth; + Eleventh = eleventh; + Twelfth = twelfth; + Thirteenth = thirteenth; + Fourteenth = fourteenth; + Fifteenth = fifteenth; + } + + internal IObservable First { get; } + internal IObservable Second { get; } + internal IObservable Third { get; } + internal IObservable Fourth { get; } + internal IObservable Fifth { get; } + internal IObservable Sixth { get; } + internal IObservable Seventh { get; } + internal IObservable Eighth { get; } + internal IObservable Ninth { get; } + internal IObservable Tenth { get; } + internal IObservable Eleventh { get; } + internal IObservable Twelfth { get; } + internal IObservable Thirteenth { get; } + internal IObservable Fourteenth { get; } + internal IObservable Fifteenth { get; } + + /// + /// Creates a pattern that matches when all sixteen observable sequences have an available element. + /// + /// The type of the elements in the sixteenth observable sequence. + /// Observable sequence to match with the fifteen previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public Pattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return new Pattern(First, Second, Third, Fourth, Fifth, Sixth, Seventh, Eighth, Ninth, Tenth, Eleventh, Twelfth, Thirteenth, Fourteenth, Fifteenth, other); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public Plan Then(Func selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return new Plan(this, selector); + } + } + + /// + /// Represents a join pattern over sixteen observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the fourteenth source sequence. + /// The type of the elements in the fifteenth source sequence. + /// The type of the elements in the sixteenth source sequence. + public class Pattern : Pattern + { + internal Pattern(IObservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth, IObservable ninth, IObservable tenth, IObservable eleventh, IObservable twelfth, IObservable thirteenth, IObservable fourteenth, IObservable fifteenth, IObservable sixteenth) + { + First = first; + Second = second; + Third = third; + Fourth = fourth; + Fifth = fifth; + Sixth = sixth; + Seventh = seventh; + Eighth = eighth; + Ninth = ninth; + Tenth = tenth; + Eleventh = eleventh; + Twelfth = twelfth; + Thirteenth = thirteenth; + Fourteenth = fourteenth; + Fifteenth = fifteenth; + Sixteenth = sixteenth; + } + + internal IObservable First { get; } + internal IObservable Second { get; } + internal IObservable Third { get; } + internal IObservable Fourth { get; } + internal IObservable Fifth { get; } + internal IObservable Sixth { get; } + internal IObservable Seventh { get; } + internal IObservable Eighth { get; } + internal IObservable Ninth { get; } + internal IObservable Tenth { get; } + internal IObservable Eleventh { get; } + internal IObservable Twelfth { get; } + internal IObservable Thirteenth { get; } + internal IObservable Fourteenth { get; } + internal IObservable Fifteenth { get; } + internal IObservable Sixteenth { get; } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public Plan Then(Func selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return new Plan(this, selector); + } + } + + #endregion +} diff --git a/LibExternal/System.Reactive/Joins/Pattern.cs b/LibExternal/System.Reactive/Joins/Pattern.cs new file mode 100644 index 0000000..df77f0a --- /dev/null +++ b/LibExternal/System.Reactive/Joins/Pattern.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Joins +{ + /// + /// Abstract base class for join patterns. + /// + public abstract class Pattern + { + internal Pattern() + { + } + } +} diff --git a/LibExternal/System.Reactive/Joins/Pattern.tt b/LibExternal/System.Reactive/Joins/Pattern.tt new file mode 100644 index 0000000..89f4453 --- /dev/null +++ b/LibExternal/System.Reactive/Joins/Pattern.tt @@ -0,0 +1,110 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".Generated.cs" #> +namespace System.Reactive.Joins +{ + /* The following code is generated by a T4 template. */ + + #region Joins auto-generated code (<#=DateTime.Now#>) + +<# +Func toUpper = s => char.ToUpper(s[0]) + s.Substring(1); + +string[] counts = new[] { "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen" }; +string[] ordinals = new[] { "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth", "tenth", "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", "sixteenth" }; + +for (int i = 1; i <= 16; i++) +{ + var parameters = string.Join(", ", Enumerable.Range(1, i).Select(j => "IObservable " + ordinals[j - 1])); + var genArgs = string.Join(", ", Enumerable.Range(1, i).Select(j => "TSource" + j)); + var sources = string.Join(", ", Enumerable.Range(1, i).Select(j => toUpper(ordinals[j - 1]))); + +#> + /// + /// Represents a join pattern over <#=counts[i - 1]#> observable sequence<#=i != 1 ? "s" : ""#>. + /// +<# +for (int j = 0; j < i; j++) +{ +#> + /// The type of the elements in the <#=ordinals[j]#> source sequence. +<# +} +#> + public class Pattern<<#=genArgs#>> : Pattern + { + internal Pattern(<#=parameters#>) + { +<# +for (int j = 0; j < i; j++) +{ +#> + <#=toUpper(ordinals[j])#> = <#=ordinals[j]#>; +<# +} +#> + } + +<# +for (int j = 0; j < i; j++) +{ +#> + internal IObservable> <#=toUpper(ordinals[j])#> { get; } +<# +} +#> + +<# +if (i > 1 && i != 16) +{ +#> + /// + /// Creates a pattern that matches when all <#=counts[i]#> observable sequences have an available element. + /// + /// The type of the elements in the <#=ordinals[i]#> observable sequence. + /// Observable sequence to match with the <#=counts[i - 1]#> previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public Pattern<<#=genArgs#>, TSource<#=i+1#>> And>(IObservable> other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return new Pattern<<#=genArgs#>, TSource<#=i+1#>>(<#=sources#>, other); + } + +<# +} +#> + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public Plan Then(Func<<#=genArgs#>, TResult> selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return new Plan<<#=genArgs#>, TResult>(this, selector); + } + } + +<# +} +#> + #endregion +} diff --git a/LibExternal/System.Reactive/Joins/Plan.cs b/LibExternal/System.Reactive/Joins/Plan.cs new file mode 100644 index 0000000..d16e1cf --- /dev/null +++ b/LibExternal/System.Reactive/Joins/Plan.cs @@ -0,0 +1,1170 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive.Joins +{ + /// + /// Represents an execution plan for join patterns. + /// + /// The type of the results produced by the plan. + public abstract class Plan + { + internal Plan() + { + } + + internal abstract ActivePlan Activate(Dictionary externalSubscriptions, + IObserver observer, Action deactivate); + + internal static JoinObserver CreateObserver( + Dictionary externalSubscriptions, IObservable observable, Action onError) + { + JoinObserver observer; + + if (!externalSubscriptions.TryGetValue(observable, out var nonGeneric)) + { + observer = new JoinObserver(observable, onError); + externalSubscriptions.Add(observable, observer); + } + else + { + observer = (JoinObserver)nonGeneric; + } + + return observer; + } + } + + internal sealed class Plan : Plan + { + internal Pattern Expression { get; } + + internal Func Selector { get; } + + internal Plan(Pattern expression, Func selector) + { + Expression = expression; + Selector = selector; + } + + internal override ActivePlan Activate(Dictionary externalSubscriptions, + IObserver observer, Action deactivate) + { + var onError = new Action(observer.OnError); + var firstJoinObserver = CreateObserver(externalSubscriptions, Expression.First, onError); + var activePlan = default(ActivePlan)!; + + activePlan = new ActivePlan(firstJoinObserver, + first => + { + TResult result; + try + { + result = Selector(first); + } + catch (Exception exception) + { + observer.OnError(exception); + return; + } + observer.OnNext(result); + }, + () => + { + firstJoinObserver.RemoveActivePlan(activePlan); + deactivate(activePlan); + }); + + firstJoinObserver.AddActivePlan(activePlan); + return activePlan; + } + } + + internal sealed class Plan : Plan + { + internal Pattern Expression { get; } + + internal Func Selector { get; } + + internal Plan(Pattern expression, Func selector) + { + Expression = expression; + Selector = selector; + } + + internal override ActivePlan Activate(Dictionary externalSubscriptions, + IObserver observer, Action deactivate) + { + var onError = new Action(observer.OnError); + var firstJoinObserver = CreateObserver(externalSubscriptions, Expression.First, onError); + var secondJoinObserver = CreateObserver(externalSubscriptions, Expression.Second, onError); + var activePlan = default(ActivePlan)!; + + activePlan = new ActivePlan(firstJoinObserver, secondJoinObserver, + (first, second) => + { + TResult result; + try + { + result = Selector(first, second); + } + catch (Exception exception) + { + observer.OnError(exception); + return; + } + observer.OnNext(result); + }, + () => + { + firstJoinObserver.RemoveActivePlan(activePlan); + secondJoinObserver.RemoveActivePlan(activePlan); + deactivate(activePlan); + }); + + firstJoinObserver.AddActivePlan(activePlan); + secondJoinObserver.AddActivePlan(activePlan); + return activePlan; + } + } + + internal sealed class Plan : Plan + { + internal Pattern Expression { get; } + + internal Func Selector { get; } + + internal Plan(Pattern expression, Func selector) + { + Expression = expression; + Selector = selector; + } + + + internal override ActivePlan Activate(Dictionary externalSubscriptions, + IObserver observer, Action deactivate) + { + var onError = new Action(observer.OnError); + var firstJoinObserver = CreateObserver(externalSubscriptions, Expression.First, onError); + var secondJoinObserver = CreateObserver(externalSubscriptions, Expression.Second, onError); + var thirdJoinObserver = CreateObserver(externalSubscriptions, Expression.Third, onError); + var activePlan = default(ActivePlan)!; + + activePlan = new ActivePlan(firstJoinObserver, secondJoinObserver, thirdJoinObserver, + (first, second, third) => + { + TResult result; + try + { + result = Selector(first, second, third); + } + catch (Exception exception) + { + observer.OnError(exception); + return; + } + observer.OnNext(result); + }, + () => + { + firstJoinObserver.RemoveActivePlan(activePlan); + secondJoinObserver.RemoveActivePlan(activePlan); + thirdJoinObserver.RemoveActivePlan(activePlan); + deactivate(activePlan); + }); + + firstJoinObserver.AddActivePlan(activePlan); + secondJoinObserver.AddActivePlan(activePlan); + thirdJoinObserver.AddActivePlan(activePlan); + return activePlan; + } + } + + internal sealed class Plan : Plan + { + internal Pattern Expression { get; } + + internal Func Selector { get; } + + internal Plan(Pattern expression, + Func selector) + { + Expression = expression; + Selector = selector; + } + + internal override ActivePlan Activate(Dictionary externalSubscriptions, + IObserver observer, Action deactivate) + { + var onError = new Action(observer.OnError); + var firstJoinObserver = CreateObserver(externalSubscriptions, Expression.First, onError); + var secondJoinObserver = CreateObserver(externalSubscriptions, Expression.Second, onError); + var thirdJoinObserver = CreateObserver(externalSubscriptions, Expression.Third, onError); + var fourthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fourth, onError); + var activePlan = default(ActivePlan)!; + + activePlan = new ActivePlan(firstJoinObserver, secondJoinObserver, thirdJoinObserver, fourthJoinObserver, + (first, second, third, fourth) => + { + TResult result; + try + { + result = Selector(first, second, third, fourth); + } + catch (Exception exception) + { + observer.OnError(exception); + return; + } + observer.OnNext(result); + }, + () => + { + firstJoinObserver.RemoveActivePlan(activePlan); + secondJoinObserver.RemoveActivePlan(activePlan); + thirdJoinObserver.RemoveActivePlan(activePlan); + fourthJoinObserver.RemoveActivePlan(activePlan); + deactivate(activePlan); + }); + + firstJoinObserver.AddActivePlan(activePlan); + secondJoinObserver.AddActivePlan(activePlan); + thirdJoinObserver.AddActivePlan(activePlan); + fourthJoinObserver.AddActivePlan(activePlan); + return activePlan; + } + } + + internal sealed class Plan : Plan + { + internal Pattern Expression { get; } + + internal Func Selector { get; } + + internal Plan(Pattern expression, + Func selector) + { + Expression = expression; + Selector = selector; + } + + internal override ActivePlan Activate(Dictionary externalSubscriptions, + IObserver observer, Action deactivate) + { + var onError = new Action(observer.OnError); + var firstJoinObserver = CreateObserver(externalSubscriptions, Expression.First, onError); + var secondJoinObserver = CreateObserver(externalSubscriptions, Expression.Second, onError); + var thirdJoinObserver = CreateObserver(externalSubscriptions, Expression.Third, onError); + var fourthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fourth, onError); + var fifthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fifth, onError); + var activePlan = default(ActivePlan)!; + + activePlan = new ActivePlan(firstJoinObserver, secondJoinObserver, thirdJoinObserver, fourthJoinObserver, fifthJoinObserver, + (first, second, third, fourth, fifth) => + { + TResult result; + try + { + result = Selector(first, second, third, fourth, fifth); + } + catch (Exception exception) + { + observer.OnError(exception); + return; + } + observer.OnNext(result); + }, + () => + { + firstJoinObserver.RemoveActivePlan(activePlan); + secondJoinObserver.RemoveActivePlan(activePlan); + thirdJoinObserver.RemoveActivePlan(activePlan); + fourthJoinObserver.RemoveActivePlan(activePlan); + fifthJoinObserver.RemoveActivePlan(activePlan); + deactivate(activePlan); + }); + + firstJoinObserver.AddActivePlan(activePlan); + secondJoinObserver.AddActivePlan(activePlan); + thirdJoinObserver.AddActivePlan(activePlan); + fourthJoinObserver.AddActivePlan(activePlan); + fifthJoinObserver.AddActivePlan(activePlan); + return activePlan; + } + } + + internal sealed class Plan : Plan + { + internal Pattern Expression { get; } + + internal Func Selector { get; } + + internal Plan(Pattern expression, + Func selector) + { + Expression = expression; + Selector = selector; + } + + internal override ActivePlan Activate(Dictionary externalSubscriptions, + IObserver observer, Action deactivate) + { + var onError = new Action(observer.OnError); + var firstJoinObserver = CreateObserver(externalSubscriptions, Expression.First, onError); + var secondJoinObserver = CreateObserver(externalSubscriptions, Expression.Second, onError); + var thirdJoinObserver = CreateObserver(externalSubscriptions, Expression.Third, onError); + var fourthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fourth, onError); + var fifthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fifth, onError); + var sixthJoinObserver = CreateObserver(externalSubscriptions, Expression.Sixth, onError); + var activePlan = default(ActivePlan)!; + + activePlan = new ActivePlan(firstJoinObserver, secondJoinObserver, thirdJoinObserver, + fourthJoinObserver, fifthJoinObserver, sixthJoinObserver, + (first, second, third, fourth, fifth, sixth) => + { + TResult result; + try + { + result = Selector(first, second, third, fourth, fifth, sixth); + } + catch (Exception exception) + { + observer.OnError(exception); + return; + } + observer.OnNext(result); + }, + () => + { + firstJoinObserver.RemoveActivePlan(activePlan); + secondJoinObserver.RemoveActivePlan(activePlan); + thirdJoinObserver.RemoveActivePlan(activePlan); + fourthJoinObserver.RemoveActivePlan(activePlan); + fifthJoinObserver.RemoveActivePlan(activePlan); + sixthJoinObserver.RemoveActivePlan(activePlan); + deactivate(activePlan); + }); + + firstJoinObserver.AddActivePlan(activePlan); + secondJoinObserver.AddActivePlan(activePlan); + thirdJoinObserver.AddActivePlan(activePlan); + fourthJoinObserver.AddActivePlan(activePlan); + fifthJoinObserver.AddActivePlan(activePlan); + sixthJoinObserver.AddActivePlan(activePlan); + return activePlan; + } + } + + internal sealed class Plan : Plan + { + internal Pattern Expression { get; } + + internal Func Selector { get; } + + internal Plan(Pattern expression, + Func selector) + { + Expression = expression; + Selector = selector; + } + + internal override ActivePlan Activate(Dictionary externalSubscriptions, + IObserver observer, Action deactivate) + { + var onError = new Action(observer.OnError); + var firstJoinObserver = CreateObserver(externalSubscriptions, Expression.First, onError); + var secondJoinObserver = CreateObserver(externalSubscriptions, Expression.Second, onError); + var thirdJoinObserver = CreateObserver(externalSubscriptions, Expression.Third, onError); + var fourthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fourth, onError); + var fifthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fifth, onError); + var sixthJoinObserver = CreateObserver(externalSubscriptions, Expression.Sixth, onError); + var seventhJoinObserver = CreateObserver(externalSubscriptions, Expression.Seventh, onError); + var activePlan = default(ActivePlan)!; + + activePlan = new ActivePlan(firstJoinObserver, secondJoinObserver, thirdJoinObserver, + fourthJoinObserver, fifthJoinObserver, sixthJoinObserver, seventhJoinObserver, + (first, second, third, fourth, fifth, sixth, seventh) => + { + TResult result; + try + { + result = Selector(first, second, third, fourth, fifth, sixth, seventh); + } + catch (Exception exception) + { + observer.OnError(exception); + return; + } + observer.OnNext(result); + }, + () => + { + firstJoinObserver.RemoveActivePlan(activePlan); + secondJoinObserver.RemoveActivePlan(activePlan); + thirdJoinObserver.RemoveActivePlan(activePlan); + fourthJoinObserver.RemoveActivePlan(activePlan); + fifthJoinObserver.RemoveActivePlan(activePlan); + sixthJoinObserver.RemoveActivePlan(activePlan); + seventhJoinObserver.RemoveActivePlan(activePlan); + deactivate(activePlan); + }); + + firstJoinObserver.AddActivePlan(activePlan); + secondJoinObserver.AddActivePlan(activePlan); + thirdJoinObserver.AddActivePlan(activePlan); + fourthJoinObserver.AddActivePlan(activePlan); + fifthJoinObserver.AddActivePlan(activePlan); + sixthJoinObserver.AddActivePlan(activePlan); + seventhJoinObserver.AddActivePlan(activePlan); + return activePlan; + } + } + + internal sealed class Plan : Plan + { + internal Pattern Expression { get; } + + internal Func Selector { get; } + + internal Plan(Pattern expression, + Func selector) + { + Expression = expression; + Selector = selector; + } + + internal override ActivePlan Activate(Dictionary externalSubscriptions, + IObserver observer, Action deactivate) + { + var onError = new Action(observer.OnError); + var firstJoinObserver = CreateObserver(externalSubscriptions, Expression.First, onError); + var secondJoinObserver = CreateObserver(externalSubscriptions, Expression.Second, onError); + var thirdJoinObserver = CreateObserver(externalSubscriptions, Expression.Third, onError); + var fourthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fourth, onError); + var fifthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fifth, onError); + var sixthJoinObserver = CreateObserver(externalSubscriptions, Expression.Sixth, onError); + var seventhJoinObserver = CreateObserver(externalSubscriptions, Expression.Seventh, onError); + var eighthJoinObserver = CreateObserver(externalSubscriptions, Expression.Eighth, onError); + var activePlan = default(ActivePlan)!; + + activePlan = new ActivePlan(firstJoinObserver, secondJoinObserver, thirdJoinObserver, + fourthJoinObserver, fifthJoinObserver, sixthJoinObserver, seventhJoinObserver, eighthJoinObserver, + (first, second, third, fourth, fifth, sixth, seventh, eighth) => + { + TResult result; + try + { + result = Selector(first, second, third, fourth, fifth, sixth, seventh, eighth); + } + catch (Exception exception) + { + observer.OnError(exception); + return; + } + observer.OnNext(result); + }, + () => + { + firstJoinObserver.RemoveActivePlan(activePlan); + secondJoinObserver.RemoveActivePlan(activePlan); + thirdJoinObserver.RemoveActivePlan(activePlan); + fourthJoinObserver.RemoveActivePlan(activePlan); + fifthJoinObserver.RemoveActivePlan(activePlan); + sixthJoinObserver.RemoveActivePlan(activePlan); + seventhJoinObserver.RemoveActivePlan(activePlan); + eighthJoinObserver.RemoveActivePlan(activePlan); + deactivate(activePlan); + }); + + firstJoinObserver.AddActivePlan(activePlan); + secondJoinObserver.AddActivePlan(activePlan); + thirdJoinObserver.AddActivePlan(activePlan); + fourthJoinObserver.AddActivePlan(activePlan); + fifthJoinObserver.AddActivePlan(activePlan); + sixthJoinObserver.AddActivePlan(activePlan); + seventhJoinObserver.AddActivePlan(activePlan); + eighthJoinObserver.AddActivePlan(activePlan); + return activePlan; + } + } + + internal sealed class Plan : Plan + { + internal Pattern Expression { get; } + + internal Func Selector { get; } + + internal Plan(Pattern expression, + Func selector) + { + Expression = expression; + Selector = selector; + } + + internal override ActivePlan Activate(Dictionary externalSubscriptions, + IObserver observer, Action deactivate) + { + var onError = new Action(observer.OnError); + var firstJoinObserver = CreateObserver(externalSubscriptions, Expression.First, onError); + var secondJoinObserver = CreateObserver(externalSubscriptions, Expression.Second, onError); + var thirdJoinObserver = CreateObserver(externalSubscriptions, Expression.Third, onError); + var fourthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fourth, onError); + var fifthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fifth, onError); + var sixthJoinObserver = CreateObserver(externalSubscriptions, Expression.Sixth, onError); + var seventhJoinObserver = CreateObserver(externalSubscriptions, Expression.Seventh, onError); + var eighthJoinObserver = CreateObserver(externalSubscriptions, Expression.Eighth, onError); + var ninthJoinObserver = CreateObserver(externalSubscriptions, Expression.Ninth, onError); + var activePlan = default(ActivePlan)!; + + activePlan = new ActivePlan(firstJoinObserver, secondJoinObserver, thirdJoinObserver, + fourthJoinObserver, fifthJoinObserver, sixthJoinObserver, seventhJoinObserver, eighthJoinObserver, ninthJoinObserver, + (first, second, third, fourth, fifth, sixth, seventh, eighth, ninth) => + { + TResult result; + try + { + result = Selector(first, second, third, fourth, fifth, sixth, seventh, eighth, ninth); + } + catch (Exception exception) + { + observer.OnError(exception); + return; + } + observer.OnNext(result); + }, + () => + { + firstJoinObserver.RemoveActivePlan(activePlan); + secondJoinObserver.RemoveActivePlan(activePlan); + thirdJoinObserver.RemoveActivePlan(activePlan); + fourthJoinObserver.RemoveActivePlan(activePlan); + fifthJoinObserver.RemoveActivePlan(activePlan); + sixthJoinObserver.RemoveActivePlan(activePlan); + seventhJoinObserver.RemoveActivePlan(activePlan); + eighthJoinObserver.RemoveActivePlan(activePlan); + ninthJoinObserver.RemoveActivePlan(activePlan); + deactivate(activePlan); + }); + + firstJoinObserver.AddActivePlan(activePlan); + secondJoinObserver.AddActivePlan(activePlan); + thirdJoinObserver.AddActivePlan(activePlan); + fourthJoinObserver.AddActivePlan(activePlan); + fifthJoinObserver.AddActivePlan(activePlan); + sixthJoinObserver.AddActivePlan(activePlan); + seventhJoinObserver.AddActivePlan(activePlan); + eighthJoinObserver.AddActivePlan(activePlan); + ninthJoinObserver.AddActivePlan(activePlan); + return activePlan; + } + } + + internal sealed class Plan : Plan + { + internal Pattern Expression { get; } + + internal Func Selector { get; } + + internal Plan(Pattern expression, + Func selector) + { + Expression = expression; + Selector = selector; + } + + internal override ActivePlan Activate(Dictionary externalSubscriptions, + IObserver observer, Action deactivate) + { + var onError = new Action(observer.OnError); + var firstJoinObserver = CreateObserver(externalSubscriptions, Expression.First, onError); + var secondJoinObserver = CreateObserver(externalSubscriptions, Expression.Second, onError); + var thirdJoinObserver = CreateObserver(externalSubscriptions, Expression.Third, onError); + var fourthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fourth, onError); + var fifthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fifth, onError); + var sixthJoinObserver = CreateObserver(externalSubscriptions, Expression.Sixth, onError); + var seventhJoinObserver = CreateObserver(externalSubscriptions, Expression.Seventh, onError); + var eighthJoinObserver = CreateObserver(externalSubscriptions, Expression.Eighth, onError); + var ninthJoinObserver = CreateObserver(externalSubscriptions, Expression.Ninth, onError); + var tenthJoinObserver = CreateObserver(externalSubscriptions, Expression.Tenth, onError); + var activePlan = default(ActivePlan)!; + + activePlan = new ActivePlan(firstJoinObserver, secondJoinObserver, thirdJoinObserver, + fourthJoinObserver, fifthJoinObserver, sixthJoinObserver, seventhJoinObserver, eighthJoinObserver, ninthJoinObserver, tenthJoinObserver, + (first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth) => + { + TResult result; + try + { + result = Selector(first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth); + } + catch (Exception exception) + { + observer.OnError(exception); + return; + } + observer.OnNext(result); + }, + () => + { + firstJoinObserver.RemoveActivePlan(activePlan); + secondJoinObserver.RemoveActivePlan(activePlan); + thirdJoinObserver.RemoveActivePlan(activePlan); + fourthJoinObserver.RemoveActivePlan(activePlan); + fifthJoinObserver.RemoveActivePlan(activePlan); + sixthJoinObserver.RemoveActivePlan(activePlan); + seventhJoinObserver.RemoveActivePlan(activePlan); + eighthJoinObserver.RemoveActivePlan(activePlan); + ninthJoinObserver.RemoveActivePlan(activePlan); + tenthJoinObserver.RemoveActivePlan(activePlan); + deactivate(activePlan); + }); + + firstJoinObserver.AddActivePlan(activePlan); + secondJoinObserver.AddActivePlan(activePlan); + thirdJoinObserver.AddActivePlan(activePlan); + fourthJoinObserver.AddActivePlan(activePlan); + fifthJoinObserver.AddActivePlan(activePlan); + sixthJoinObserver.AddActivePlan(activePlan); + seventhJoinObserver.AddActivePlan(activePlan); + eighthJoinObserver.AddActivePlan(activePlan); + ninthJoinObserver.AddActivePlan(activePlan); + tenthJoinObserver.AddActivePlan(activePlan); + return activePlan; + } + } + + internal sealed class Plan : Plan + { + internal Pattern Expression { get; } + + internal Func Selector { get; } + + internal Plan(Pattern expression, + Func selector) + { + Expression = expression; + Selector = selector; + } + + internal override ActivePlan Activate(Dictionary externalSubscriptions, + IObserver observer, Action deactivate) + { + var onError = new Action(observer.OnError); + var firstJoinObserver = CreateObserver(externalSubscriptions, Expression.First, onError); + var secondJoinObserver = CreateObserver(externalSubscriptions, Expression.Second, onError); + var thirdJoinObserver = CreateObserver(externalSubscriptions, Expression.Third, onError); + var fourthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fourth, onError); + var fifthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fifth, onError); + var sixthJoinObserver = CreateObserver(externalSubscriptions, Expression.Sixth, onError); + var seventhJoinObserver = CreateObserver(externalSubscriptions, Expression.Seventh, onError); + var eighthJoinObserver = CreateObserver(externalSubscriptions, Expression.Eighth, onError); + var ninthJoinObserver = CreateObserver(externalSubscriptions, Expression.Ninth, onError); + var tenthJoinObserver = CreateObserver(externalSubscriptions, Expression.Tenth, onError); + var eleventhJoinObserver = CreateObserver(externalSubscriptions, Expression.Eleventh, onError); + + var activePlan = default(ActivePlan)!; + + activePlan = new ActivePlan(firstJoinObserver, secondJoinObserver, thirdJoinObserver, + fourthJoinObserver, fifthJoinObserver, sixthJoinObserver, seventhJoinObserver, eighthJoinObserver, ninthJoinObserver, tenthJoinObserver, eleventhJoinObserver, + (first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh) => + { + TResult result; + try + { + result = Selector(first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh); + } + catch (Exception exception) + { + observer.OnError(exception); + return; + } + observer.OnNext(result); + }, + () => + { + firstJoinObserver.RemoveActivePlan(activePlan); + secondJoinObserver.RemoveActivePlan(activePlan); + thirdJoinObserver.RemoveActivePlan(activePlan); + fourthJoinObserver.RemoveActivePlan(activePlan); + fifthJoinObserver.RemoveActivePlan(activePlan); + sixthJoinObserver.RemoveActivePlan(activePlan); + seventhJoinObserver.RemoveActivePlan(activePlan); + eighthJoinObserver.RemoveActivePlan(activePlan); + ninthJoinObserver.RemoveActivePlan(activePlan); + tenthJoinObserver.RemoveActivePlan(activePlan); + eleventhJoinObserver.RemoveActivePlan(activePlan); + deactivate(activePlan); + }); + + firstJoinObserver.AddActivePlan(activePlan); + secondJoinObserver.AddActivePlan(activePlan); + thirdJoinObserver.AddActivePlan(activePlan); + fourthJoinObserver.AddActivePlan(activePlan); + fifthJoinObserver.AddActivePlan(activePlan); + sixthJoinObserver.AddActivePlan(activePlan); + seventhJoinObserver.AddActivePlan(activePlan); + eighthJoinObserver.AddActivePlan(activePlan); + ninthJoinObserver.AddActivePlan(activePlan); + tenthJoinObserver.AddActivePlan(activePlan); + eleventhJoinObserver.AddActivePlan(activePlan); + return activePlan; + } + } + + internal sealed class Plan : Plan + { + internal Pattern Expression { get; } + + internal Func Selector { get; } + + internal Plan(Pattern expression, + Func selector) + { + Expression = expression; + Selector = selector; + } + + internal override ActivePlan Activate(Dictionary externalSubscriptions, + IObserver observer, Action deactivate) + { + var onError = new Action(observer.OnError); + var firstJoinObserver = CreateObserver(externalSubscriptions, Expression.First, onError); + var secondJoinObserver = CreateObserver(externalSubscriptions, Expression.Second, onError); + var thirdJoinObserver = CreateObserver(externalSubscriptions, Expression.Third, onError); + var fourthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fourth, onError); + var fifthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fifth, onError); + var sixthJoinObserver = CreateObserver(externalSubscriptions, Expression.Sixth, onError); + var seventhJoinObserver = CreateObserver(externalSubscriptions, Expression.Seventh, onError); + var eighthJoinObserver = CreateObserver(externalSubscriptions, Expression.Eighth, onError); + var ninthJoinObserver = CreateObserver(externalSubscriptions, Expression.Ninth, onError); + var tenthJoinObserver = CreateObserver(externalSubscriptions, Expression.Tenth, onError); + var eleventhJoinObserver = CreateObserver(externalSubscriptions, Expression.Eleventh, onError); + var twelfthJoinObserver = CreateObserver(externalSubscriptions, Expression.Twelfth, onError); + + var activePlan = default(ActivePlan)!; + + activePlan = new ActivePlan(firstJoinObserver, secondJoinObserver, thirdJoinObserver, + fourthJoinObserver, fifthJoinObserver, sixthJoinObserver, seventhJoinObserver, eighthJoinObserver, ninthJoinObserver, tenthJoinObserver, eleventhJoinObserver, + twelfthJoinObserver, + (first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth) => + { + TResult result; + try + { + result = Selector(first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth); + } + catch (Exception exception) + { + observer.OnError(exception); + return; + } + observer.OnNext(result); + }, + () => + { + firstJoinObserver.RemoveActivePlan(activePlan); + secondJoinObserver.RemoveActivePlan(activePlan); + thirdJoinObserver.RemoveActivePlan(activePlan); + fourthJoinObserver.RemoveActivePlan(activePlan); + fifthJoinObserver.RemoveActivePlan(activePlan); + sixthJoinObserver.RemoveActivePlan(activePlan); + seventhJoinObserver.RemoveActivePlan(activePlan); + eighthJoinObserver.RemoveActivePlan(activePlan); + ninthJoinObserver.RemoveActivePlan(activePlan); + tenthJoinObserver.RemoveActivePlan(activePlan); + eleventhJoinObserver.RemoveActivePlan(activePlan); + twelfthJoinObserver.RemoveActivePlan(activePlan); + deactivate(activePlan); + }); + + firstJoinObserver.AddActivePlan(activePlan); + secondJoinObserver.AddActivePlan(activePlan); + thirdJoinObserver.AddActivePlan(activePlan); + fourthJoinObserver.AddActivePlan(activePlan); + fifthJoinObserver.AddActivePlan(activePlan); + sixthJoinObserver.AddActivePlan(activePlan); + seventhJoinObserver.AddActivePlan(activePlan); + eighthJoinObserver.AddActivePlan(activePlan); + ninthJoinObserver.AddActivePlan(activePlan); + tenthJoinObserver.AddActivePlan(activePlan); + eleventhJoinObserver.AddActivePlan(activePlan); + twelfthJoinObserver.AddActivePlan(activePlan); + return activePlan; + } + } + + + internal sealed class Plan : Plan + { + internal Pattern Expression { get; } + + internal Func Selector { get; } + + internal Plan(Pattern expression, + Func selector) + { + Expression = expression; + Selector = selector; + } + + internal override ActivePlan Activate(Dictionary externalSubscriptions, + IObserver observer, Action deactivate) + { + var onError = new Action(observer.OnError); + var firstJoinObserver = CreateObserver(externalSubscriptions, Expression.First, onError); + var secondJoinObserver = CreateObserver(externalSubscriptions, Expression.Second, onError); + var thirdJoinObserver = CreateObserver(externalSubscriptions, Expression.Third, onError); + var fourthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fourth, onError); + var fifthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fifth, onError); + var sixthJoinObserver = CreateObserver(externalSubscriptions, Expression.Sixth, onError); + var seventhJoinObserver = CreateObserver(externalSubscriptions, Expression.Seventh, onError); + var eighthJoinObserver = CreateObserver(externalSubscriptions, Expression.Eighth, onError); + var ninthJoinObserver = CreateObserver(externalSubscriptions, Expression.Ninth, onError); + var tenthJoinObserver = CreateObserver(externalSubscriptions, Expression.Tenth, onError); + var eleventhJoinObserver = CreateObserver(externalSubscriptions, Expression.Eleventh, onError); + var twelfthJoinObserver = CreateObserver(externalSubscriptions, Expression.Twelfth, onError); + var thirteenthJoinObserver = CreateObserver(externalSubscriptions, Expression.Thirteenth, onError); + + var activePlan = default(ActivePlan)!; + + activePlan = new ActivePlan(firstJoinObserver, secondJoinObserver, thirdJoinObserver, + fourthJoinObserver, fifthJoinObserver, sixthJoinObserver, seventhJoinObserver, eighthJoinObserver, ninthJoinObserver, tenthJoinObserver, eleventhJoinObserver, + twelfthJoinObserver, thirteenthJoinObserver, + (first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth, thirteenth) => + { + TResult result; + try + { + result = Selector(first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth, thirteenth); + } + catch (Exception exception) + { + observer.OnError(exception); + return; + } + observer.OnNext(result); + }, + () => + { + firstJoinObserver.RemoveActivePlan(activePlan); + secondJoinObserver.RemoveActivePlan(activePlan); + thirdJoinObserver.RemoveActivePlan(activePlan); + fourthJoinObserver.RemoveActivePlan(activePlan); + fifthJoinObserver.RemoveActivePlan(activePlan); + sixthJoinObserver.RemoveActivePlan(activePlan); + seventhJoinObserver.RemoveActivePlan(activePlan); + eighthJoinObserver.RemoveActivePlan(activePlan); + ninthJoinObserver.RemoveActivePlan(activePlan); + tenthJoinObserver.RemoveActivePlan(activePlan); + eleventhJoinObserver.RemoveActivePlan(activePlan); + twelfthJoinObserver.RemoveActivePlan(activePlan); + thirteenthJoinObserver.RemoveActivePlan(activePlan); + deactivate(activePlan); + }); + + firstJoinObserver.AddActivePlan(activePlan); + secondJoinObserver.AddActivePlan(activePlan); + thirdJoinObserver.AddActivePlan(activePlan); + fourthJoinObserver.AddActivePlan(activePlan); + fifthJoinObserver.AddActivePlan(activePlan); + sixthJoinObserver.AddActivePlan(activePlan); + seventhJoinObserver.AddActivePlan(activePlan); + eighthJoinObserver.AddActivePlan(activePlan); + ninthJoinObserver.AddActivePlan(activePlan); + tenthJoinObserver.AddActivePlan(activePlan); + eleventhJoinObserver.AddActivePlan(activePlan); + twelfthJoinObserver.AddActivePlan(activePlan); + thirteenthJoinObserver.AddActivePlan(activePlan); + return activePlan; + } + } + + + internal sealed class Plan : Plan + { + internal Pattern Expression { get; } + + internal Func Selector { get; } + + internal Plan(Pattern expression, + Func selector) + { + Expression = expression; + Selector = selector; + } + + internal override ActivePlan Activate(Dictionary externalSubscriptions, + IObserver observer, Action deactivate) + { + var onError = new Action(observer.OnError); + var firstJoinObserver = CreateObserver(externalSubscriptions, Expression.First, onError); + var secondJoinObserver = CreateObserver(externalSubscriptions, Expression.Second, onError); + var thirdJoinObserver = CreateObserver(externalSubscriptions, Expression.Third, onError); + var fourthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fourth, onError); + var fifthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fifth, onError); + var sixthJoinObserver = CreateObserver(externalSubscriptions, Expression.Sixth, onError); + var seventhJoinObserver = CreateObserver(externalSubscriptions, Expression.Seventh, onError); + var eighthJoinObserver = CreateObserver(externalSubscriptions, Expression.Eighth, onError); + var ninthJoinObserver = CreateObserver(externalSubscriptions, Expression.Ninth, onError); + var tenthJoinObserver = CreateObserver(externalSubscriptions, Expression.Tenth, onError); + var eleventhJoinObserver = CreateObserver(externalSubscriptions, Expression.Eleventh, onError); + var twelfthJoinObserver = CreateObserver(externalSubscriptions, Expression.Twelfth, onError); + var thirteenthJoinObserver = CreateObserver(externalSubscriptions, Expression.Thirteenth, onError); + var fourteenthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fourteenth, onError); + + var activePlan = default(ActivePlan)!; + + activePlan = new ActivePlan( + firstJoinObserver, secondJoinObserver, thirdJoinObserver, + fourthJoinObserver, fifthJoinObserver, sixthJoinObserver, + seventhJoinObserver, eighthJoinObserver, ninthJoinObserver, + tenthJoinObserver, eleventhJoinObserver, + twelfthJoinObserver, thirteenthJoinObserver, + fourteenthJoinObserver, + (first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth, thirteenth, fourteenth) => + { + TResult result; + try + { + result = Selector(first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth, thirteenth, fourteenth); + } + catch (Exception exception) + { + observer.OnError(exception); + return; + } + observer.OnNext(result); + }, + () => + { + firstJoinObserver.RemoveActivePlan(activePlan); + secondJoinObserver.RemoveActivePlan(activePlan); + thirdJoinObserver.RemoveActivePlan(activePlan); + fourthJoinObserver.RemoveActivePlan(activePlan); + fifthJoinObserver.RemoveActivePlan(activePlan); + sixthJoinObserver.RemoveActivePlan(activePlan); + seventhJoinObserver.RemoveActivePlan(activePlan); + eighthJoinObserver.RemoveActivePlan(activePlan); + ninthJoinObserver.RemoveActivePlan(activePlan); + tenthJoinObserver.RemoveActivePlan(activePlan); + eleventhJoinObserver.RemoveActivePlan(activePlan); + twelfthJoinObserver.RemoveActivePlan(activePlan); + thirteenthJoinObserver.RemoveActivePlan(activePlan); + fourteenthJoinObserver.RemoveActivePlan(activePlan); + deactivate(activePlan); + }); + + firstJoinObserver.AddActivePlan(activePlan); + secondJoinObserver.AddActivePlan(activePlan); + thirdJoinObserver.AddActivePlan(activePlan); + fourthJoinObserver.AddActivePlan(activePlan); + fifthJoinObserver.AddActivePlan(activePlan); + sixthJoinObserver.AddActivePlan(activePlan); + seventhJoinObserver.AddActivePlan(activePlan); + eighthJoinObserver.AddActivePlan(activePlan); + ninthJoinObserver.AddActivePlan(activePlan); + tenthJoinObserver.AddActivePlan(activePlan); + eleventhJoinObserver.AddActivePlan(activePlan); + twelfthJoinObserver.AddActivePlan(activePlan); + thirteenthJoinObserver.AddActivePlan(activePlan); + fourteenthJoinObserver.AddActivePlan(activePlan); + return activePlan; + } + } + + internal sealed class Plan : Plan + { + internal Pattern Expression { get; } + + internal Func Selector { get; } + + internal Plan(Pattern expression, + Func selector) + { + Expression = expression; + Selector = selector; + } + + internal override ActivePlan Activate(Dictionary externalSubscriptions, + IObserver observer, Action deactivate) + { + var onError = new Action(observer.OnError); + var firstJoinObserver = CreateObserver(externalSubscriptions, Expression.First, onError); + var secondJoinObserver = CreateObserver(externalSubscriptions, Expression.Second, onError); + var thirdJoinObserver = CreateObserver(externalSubscriptions, Expression.Third, onError); + var fourthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fourth, onError); + var fifthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fifth, onError); + var sixthJoinObserver = CreateObserver(externalSubscriptions, Expression.Sixth, onError); + var seventhJoinObserver = CreateObserver(externalSubscriptions, Expression.Seventh, onError); + var eighthJoinObserver = CreateObserver(externalSubscriptions, Expression.Eighth, onError); + var ninthJoinObserver = CreateObserver(externalSubscriptions, Expression.Ninth, onError); + var tenthJoinObserver = CreateObserver(externalSubscriptions, Expression.Tenth, onError); + var eleventhJoinObserver = CreateObserver(externalSubscriptions, Expression.Eleventh, onError); + var twelfthJoinObserver = CreateObserver(externalSubscriptions, Expression.Twelfth, onError); + var thirteenthJoinObserver = CreateObserver(externalSubscriptions, Expression.Thirteenth, onError); + var fourteenthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fourteenth, onError); + var fifteenthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fifteenth, onError); + + var activePlan = default(ActivePlan)!; + + activePlan = new ActivePlan( + firstJoinObserver, secondJoinObserver, thirdJoinObserver, + fourthJoinObserver, fifthJoinObserver, sixthJoinObserver, + seventhJoinObserver, eighthJoinObserver, ninthJoinObserver, + tenthJoinObserver, eleventhJoinObserver, + twelfthJoinObserver, thirteenthJoinObserver, + fourteenthJoinObserver, fifteenthJoinObserver, + (first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth, thirteenth, fourteenth, fifteenth) => + { + TResult result; + try + { + result = Selector(first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth, thirteenth, fourteenth, fifteenth); + } + catch (Exception exception) + { + observer.OnError(exception); + return; + } + observer.OnNext(result); + }, + () => + { + firstJoinObserver.RemoveActivePlan(activePlan); + secondJoinObserver.RemoveActivePlan(activePlan); + thirdJoinObserver.RemoveActivePlan(activePlan); + fourthJoinObserver.RemoveActivePlan(activePlan); + fifthJoinObserver.RemoveActivePlan(activePlan); + sixthJoinObserver.RemoveActivePlan(activePlan); + seventhJoinObserver.RemoveActivePlan(activePlan); + eighthJoinObserver.RemoveActivePlan(activePlan); + ninthJoinObserver.RemoveActivePlan(activePlan); + tenthJoinObserver.RemoveActivePlan(activePlan); + eleventhJoinObserver.RemoveActivePlan(activePlan); + twelfthJoinObserver.RemoveActivePlan(activePlan); + thirteenthJoinObserver.RemoveActivePlan(activePlan); + fourteenthJoinObserver.RemoveActivePlan(activePlan); + fifteenthJoinObserver.RemoveActivePlan(activePlan); + deactivate(activePlan); + }); + + firstJoinObserver.AddActivePlan(activePlan); + secondJoinObserver.AddActivePlan(activePlan); + thirdJoinObserver.AddActivePlan(activePlan); + fourthJoinObserver.AddActivePlan(activePlan); + fifthJoinObserver.AddActivePlan(activePlan); + sixthJoinObserver.AddActivePlan(activePlan); + seventhJoinObserver.AddActivePlan(activePlan); + eighthJoinObserver.AddActivePlan(activePlan); + ninthJoinObserver.AddActivePlan(activePlan); + tenthJoinObserver.AddActivePlan(activePlan); + eleventhJoinObserver.AddActivePlan(activePlan); + twelfthJoinObserver.AddActivePlan(activePlan); + thirteenthJoinObserver.AddActivePlan(activePlan); + fourteenthJoinObserver.AddActivePlan(activePlan); + fifteenthJoinObserver.AddActivePlan(activePlan); + return activePlan; + } + } + internal sealed class Plan : Plan + { + internal Pattern Expression { get; } + + internal Func Selector { get; } + + internal Plan(Pattern expression, + Func selector) + { + Expression = expression; + Selector = selector; + } + + internal override ActivePlan Activate(Dictionary externalSubscriptions, + IObserver observer, Action deactivate) + { + var onError = new Action(observer.OnError); + var firstJoinObserver = CreateObserver(externalSubscriptions, Expression.First, onError); + var secondJoinObserver = CreateObserver(externalSubscriptions, Expression.Second, onError); + var thirdJoinObserver = CreateObserver(externalSubscriptions, Expression.Third, onError); + var fourthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fourth, onError); + var fifthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fifth, onError); + var sixthJoinObserver = CreateObserver(externalSubscriptions, Expression.Sixth, onError); + var seventhJoinObserver = CreateObserver(externalSubscriptions, Expression.Seventh, onError); + var eighthJoinObserver = CreateObserver(externalSubscriptions, Expression.Eighth, onError); + var ninthJoinObserver = CreateObserver(externalSubscriptions, Expression.Ninth, onError); + var tenthJoinObserver = CreateObserver(externalSubscriptions, Expression.Tenth, onError); + var eleventhJoinObserver = CreateObserver(externalSubscriptions, Expression.Eleventh, onError); + var twelfthJoinObserver = CreateObserver(externalSubscriptions, Expression.Twelfth, onError); + var thirteenthJoinObserver = CreateObserver(externalSubscriptions, Expression.Thirteenth, onError); + var fourteenthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fourteenth, onError); + var fifteenthJoinObserver = CreateObserver(externalSubscriptions, Expression.Fifteenth, onError); + var sixteenthJoinObserver = CreateObserver(externalSubscriptions, Expression.Sixteenth, onError); + + var activePlan = default(ActivePlan)!; + + activePlan = new ActivePlan( + firstJoinObserver, secondJoinObserver, thirdJoinObserver, + fourthJoinObserver, fifthJoinObserver, sixthJoinObserver, + seventhJoinObserver, eighthJoinObserver, ninthJoinObserver, + tenthJoinObserver, eleventhJoinObserver, + twelfthJoinObserver, thirteenthJoinObserver, + fourteenthJoinObserver, fifteenthJoinObserver, + sixteenthJoinObserver, + (first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth, thirteenth, fourteenth, fifteenth, sixteenth) => + { + TResult result; + try + { + result = Selector(first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth, thirteenth, fourteenth, fifteenth, sixteenth); + } + catch (Exception exception) + { + observer.OnError(exception); + return; + } + observer.OnNext(result); + }, + () => + { + firstJoinObserver.RemoveActivePlan(activePlan); + secondJoinObserver.RemoveActivePlan(activePlan); + thirdJoinObserver.RemoveActivePlan(activePlan); + fourthJoinObserver.RemoveActivePlan(activePlan); + fifthJoinObserver.RemoveActivePlan(activePlan); + sixthJoinObserver.RemoveActivePlan(activePlan); + seventhJoinObserver.RemoveActivePlan(activePlan); + eighthJoinObserver.RemoveActivePlan(activePlan); + ninthJoinObserver.RemoveActivePlan(activePlan); + tenthJoinObserver.RemoveActivePlan(activePlan); + eleventhJoinObserver.RemoveActivePlan(activePlan); + twelfthJoinObserver.RemoveActivePlan(activePlan); + thirteenthJoinObserver.RemoveActivePlan(activePlan); + fourteenthJoinObserver.RemoveActivePlan(activePlan); + fifteenthJoinObserver.RemoveActivePlan(activePlan); + sixteenthJoinObserver.RemoveActivePlan(activePlan); + deactivate(activePlan); + }); + + firstJoinObserver.AddActivePlan(activePlan); + secondJoinObserver.AddActivePlan(activePlan); + thirdJoinObserver.AddActivePlan(activePlan); + fourthJoinObserver.AddActivePlan(activePlan); + fifthJoinObserver.AddActivePlan(activePlan); + sixthJoinObserver.AddActivePlan(activePlan); + seventhJoinObserver.AddActivePlan(activePlan); + eighthJoinObserver.AddActivePlan(activePlan); + ninthJoinObserver.AddActivePlan(activePlan); + tenthJoinObserver.AddActivePlan(activePlan); + eleventhJoinObserver.AddActivePlan(activePlan); + twelfthJoinObserver.AddActivePlan(activePlan); + thirteenthJoinObserver.AddActivePlan(activePlan); + fourteenthJoinObserver.AddActivePlan(activePlan); + fifteenthJoinObserver.AddActivePlan(activePlan); + sixteenthJoinObserver.AddActivePlan(activePlan); + return activePlan; + } + } +} diff --git a/LibExternal/System.Reactive/Joins/QueryablePattern.Generated.cs b/LibExternal/System.Reactive/Joins/QueryablePattern.Generated.cs new file mode 100644 index 0000000..0fa012c --- /dev/null +++ b/LibExternal/System.Reactive/Joins/QueryablePattern.Generated.cs @@ -0,0 +1,1040 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Linq.Expressions; +using System.Reactive.Linq; + +namespace System.Reactive.Joins +{ + /* The following code is generated by a T4 template. */ + + #region Joins auto-generated code (10/01/2020 12:41:16) + + /// + /// Represents a join pattern over two observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + public class QueryablePattern : QueryablePattern + { + internal QueryablePattern(Expression expression) + : base(expression) + { + } + + /// + /// Creates a pattern that matches when all three observable sequences have an available element. + /// + /// The type of the elements in the third observable sequence. + /// Observable sequence to match with the two previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public QueryablePattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(And)).MakeGenericMethod(typeof(TSource3)); + return new QueryablePattern( + Expression.Call( + Expression, + m, + Qbservable.GetSourceExpression(other) + ) + ); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public QueryablePlan Then(Expression> selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(Then)).MakeGenericMethod(typeof(TResult)); + return new QueryablePlan( + Expression.Call( + Expression, + m, + selector + ) + ); + } + } + + /// + /// Represents a join pattern over three observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + public class QueryablePattern : QueryablePattern + { + internal QueryablePattern(Expression expression) + : base(expression) + { + } + + /// + /// Creates a pattern that matches when all four observable sequences have an available element. + /// + /// The type of the elements in the fourth observable sequence. + /// Observable sequence to match with the three previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public QueryablePattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(And)).MakeGenericMethod(typeof(TSource4)); + return new QueryablePattern( + Expression.Call( + Expression, + m, + Qbservable.GetSourceExpression(other) + ) + ); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public QueryablePlan Then(Expression> selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(Then)).MakeGenericMethod(typeof(TResult)); + return new QueryablePlan( + Expression.Call( + Expression, + m, + selector + ) + ); + } + } + + /// + /// Represents a join pattern over four observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + public class QueryablePattern : QueryablePattern + { + internal QueryablePattern(Expression expression) + : base(expression) + { + } + + /// + /// Creates a pattern that matches when all five observable sequences have an available element. + /// + /// The type of the elements in the fifth observable sequence. + /// Observable sequence to match with the four previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public QueryablePattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(And)).MakeGenericMethod(typeof(TSource5)); + return new QueryablePattern( + Expression.Call( + Expression, + m, + Qbservable.GetSourceExpression(other) + ) + ); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public QueryablePlan Then(Expression> selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(Then)).MakeGenericMethod(typeof(TResult)); + return new QueryablePlan( + Expression.Call( + Expression, + m, + selector + ) + ); + } + } + + /// + /// Represents a join pattern over five observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + public class QueryablePattern : QueryablePattern + { + internal QueryablePattern(Expression expression) + : base(expression) + { + } + + /// + /// Creates a pattern that matches when all six observable sequences have an available element. + /// + /// The type of the elements in the sixth observable sequence. + /// Observable sequence to match with the five previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public QueryablePattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(And)).MakeGenericMethod(typeof(TSource6)); + return new QueryablePattern( + Expression.Call( + Expression, + m, + Qbservable.GetSourceExpression(other) + ) + ); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public QueryablePlan Then(Expression> selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(Then)).MakeGenericMethod(typeof(TResult)); + return new QueryablePlan( + Expression.Call( + Expression, + m, + selector + ) + ); + } + } + + /// + /// Represents a join pattern over six observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + public class QueryablePattern : QueryablePattern + { + internal QueryablePattern(Expression expression) + : base(expression) + { + } + + /// + /// Creates a pattern that matches when all seven observable sequences have an available element. + /// + /// The type of the elements in the seventh observable sequence. + /// Observable sequence to match with the six previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public QueryablePattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(And)).MakeGenericMethod(typeof(TSource7)); + return new QueryablePattern( + Expression.Call( + Expression, + m, + Qbservable.GetSourceExpression(other) + ) + ); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public QueryablePlan Then(Expression> selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(Then)).MakeGenericMethod(typeof(TResult)); + return new QueryablePlan( + Expression.Call( + Expression, + m, + selector + ) + ); + } + } + + /// + /// Represents a join pattern over seven observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + public class QueryablePattern : QueryablePattern + { + internal QueryablePattern(Expression expression) + : base(expression) + { + } + + /// + /// Creates a pattern that matches when all eight observable sequences have an available element. + /// + /// The type of the elements in the eighth observable sequence. + /// Observable sequence to match with the seven previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public QueryablePattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(And)).MakeGenericMethod(typeof(TSource8)); + return new QueryablePattern( + Expression.Call( + Expression, + m, + Qbservable.GetSourceExpression(other) + ) + ); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public QueryablePlan Then(Expression> selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(Then)).MakeGenericMethod(typeof(TResult)); + return new QueryablePlan( + Expression.Call( + Expression, + m, + selector + ) + ); + } + } + + /// + /// Represents a join pattern over eight observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + public class QueryablePattern : QueryablePattern + { + internal QueryablePattern(Expression expression) + : base(expression) + { + } + + /// + /// Creates a pattern that matches when all nine observable sequences have an available element. + /// + /// The type of the elements in the ninth observable sequence. + /// Observable sequence to match with the eight previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public QueryablePattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(And)).MakeGenericMethod(typeof(TSource9)); + return new QueryablePattern( + Expression.Call( + Expression, + m, + Qbservable.GetSourceExpression(other) + ) + ); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public QueryablePlan Then(Expression> selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(Then)).MakeGenericMethod(typeof(TResult)); + return new QueryablePlan( + Expression.Call( + Expression, + m, + selector + ) + ); + } + } + + /// + /// Represents a join pattern over nine observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + public class QueryablePattern : QueryablePattern + { + internal QueryablePattern(Expression expression) + : base(expression) + { + } + + /// + /// Creates a pattern that matches when all ten observable sequences have an available element. + /// + /// The type of the elements in the tenth observable sequence. + /// Observable sequence to match with the nine previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public QueryablePattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(And)).MakeGenericMethod(typeof(TSource10)); + return new QueryablePattern( + Expression.Call( + Expression, + m, + Qbservable.GetSourceExpression(other) + ) + ); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public QueryablePlan Then(Expression> selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(Then)).MakeGenericMethod(typeof(TResult)); + return new QueryablePlan( + Expression.Call( + Expression, + m, + selector + ) + ); + } + } + + /// + /// Represents a join pattern over ten observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + public class QueryablePattern : QueryablePattern + { + internal QueryablePattern(Expression expression) + : base(expression) + { + } + + /// + /// Creates a pattern that matches when all eleven observable sequences have an available element. + /// + /// The type of the elements in the eleventh observable sequence. + /// Observable sequence to match with the ten previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public QueryablePattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(And)).MakeGenericMethod(typeof(TSource11)); + return new QueryablePattern( + Expression.Call( + Expression, + m, + Qbservable.GetSourceExpression(other) + ) + ); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public QueryablePlan Then(Expression> selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(Then)).MakeGenericMethod(typeof(TResult)); + return new QueryablePlan( + Expression.Call( + Expression, + m, + selector + ) + ); + } + } + + /// + /// Represents a join pattern over eleven observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + public class QueryablePattern : QueryablePattern + { + internal QueryablePattern(Expression expression) + : base(expression) + { + } + + /// + /// Creates a pattern that matches when all twelve observable sequences have an available element. + /// + /// The type of the elements in the twelfth observable sequence. + /// Observable sequence to match with the eleven previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public QueryablePattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(And)).MakeGenericMethod(typeof(TSource12)); + return new QueryablePattern( + Expression.Call( + Expression, + m, + Qbservable.GetSourceExpression(other) + ) + ); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public QueryablePlan Then(Expression> selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(Then)).MakeGenericMethod(typeof(TResult)); + return new QueryablePlan( + Expression.Call( + Expression, + m, + selector + ) + ); + } + } + + /// + /// Represents a join pattern over twelve observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + public class QueryablePattern : QueryablePattern + { + internal QueryablePattern(Expression expression) + : base(expression) + { + } + + /// + /// Creates a pattern that matches when all thirteen observable sequences have an available element. + /// + /// The type of the elements in the thirteenth observable sequence. + /// Observable sequence to match with the twelve previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public QueryablePattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(And)).MakeGenericMethod(typeof(TSource13)); + return new QueryablePattern( + Expression.Call( + Expression, + m, + Qbservable.GetSourceExpression(other) + ) + ); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public QueryablePlan Then(Expression> selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(Then)).MakeGenericMethod(typeof(TResult)); + return new QueryablePlan( + Expression.Call( + Expression, + m, + selector + ) + ); + } + } + + /// + /// Represents a join pattern over thirteen observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + public class QueryablePattern : QueryablePattern + { + internal QueryablePattern(Expression expression) + : base(expression) + { + } + + /// + /// Creates a pattern that matches when all fourteen observable sequences have an available element. + /// + /// The type of the elements in the fourteenth observable sequence. + /// Observable sequence to match with the thirteen previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public QueryablePattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(And)).MakeGenericMethod(typeof(TSource14)); + return new QueryablePattern( + Expression.Call( + Expression, + m, + Qbservable.GetSourceExpression(other) + ) + ); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public QueryablePlan Then(Expression> selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(Then)).MakeGenericMethod(typeof(TResult)); + return new QueryablePlan( + Expression.Call( + Expression, + m, + selector + ) + ); + } + } + + /// + /// Represents a join pattern over fourteen observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the fourteenth source sequence. + public class QueryablePattern : QueryablePattern + { + internal QueryablePattern(Expression expression) + : base(expression) + { + } + + /// + /// Creates a pattern that matches when all fifteen observable sequences have an available element. + /// + /// The type of the elements in the fifteenth observable sequence. + /// Observable sequence to match with the fourteen previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public QueryablePattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(And)).MakeGenericMethod(typeof(TSource15)); + return new QueryablePattern( + Expression.Call( + Expression, + m, + Qbservable.GetSourceExpression(other) + ) + ); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public QueryablePlan Then(Expression> selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(Then)).MakeGenericMethod(typeof(TResult)); + return new QueryablePlan( + Expression.Call( + Expression, + m, + selector + ) + ); + } + } + + /// + /// Represents a join pattern over fifteen observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the fourteenth source sequence. + /// The type of the elements in the fifteenth source sequence. + public class QueryablePattern : QueryablePattern + { + internal QueryablePattern(Expression expression) + : base(expression) + { + } + + /// + /// Creates a pattern that matches when all sixteen observable sequences have an available element. + /// + /// The type of the elements in the sixteenth observable sequence. + /// Observable sequence to match with the fifteen previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public QueryablePattern And(IObservable other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(And)).MakeGenericMethod(typeof(TSource16)); + return new QueryablePattern( + Expression.Call( + Expression, + m, + Qbservable.GetSourceExpression(other) + ) + ); + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public QueryablePlan Then(Expression> selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(Then)).MakeGenericMethod(typeof(TResult)); + return new QueryablePlan( + Expression.Call( + Expression, + m, + selector + ) + ); + } + } + + /// + /// Represents a join pattern over sixteen observable sequences. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the fourteenth source sequence. + /// The type of the elements in the fifteenth source sequence. + /// The type of the elements in the sixteenth source sequence. + public class QueryablePattern : QueryablePattern + { + internal QueryablePattern(Expression expression) + : base(expression) + { + } + + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public QueryablePlan Then(Expression> selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + var t = typeof(QueryablePattern); + var m = t.GetMethod(nameof(Then)).MakeGenericMethod(typeof(TResult)); + return new QueryablePlan( + Expression.Call( + Expression, + m, + selector + ) + ); + } + } + + #endregion +} diff --git a/LibExternal/System.Reactive/Joins/QueryablePattern.cs b/LibExternal/System.Reactive/Joins/QueryablePattern.cs new file mode 100644 index 0000000..93d0f42 --- /dev/null +++ b/LibExternal/System.Reactive/Joins/QueryablePattern.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Linq.Expressions; + +namespace System.Reactive.Joins +{ + /// + /// Abstract base class for join patterns represented by an expression tree. + /// + public abstract class QueryablePattern + { + /// + /// Creates a new join pattern object using the specified expression tree representation. + /// + /// Expression tree representing the join pattern. + protected QueryablePattern(Expression expression) + { + Expression = expression; + } + + /// + /// Gets the expression tree representing the join pattern. + /// + public Expression Expression { get; } + } +} diff --git a/LibExternal/System.Reactive/Joins/QueryablePattern.tt b/LibExternal/System.Reactive/Joins/QueryablePattern.tt new file mode 100644 index 0000000..9d90fd1 --- /dev/null +++ b/LibExternal/System.Reactive/Joins/QueryablePattern.tt @@ -0,0 +1,113 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".Generated.cs" #> +using System.Linq.Expressions; +using System.Reactive.Linq; + +namespace System.Reactive.Joins +{ + /* The following code is generated by a T4 template. */ + + #region Joins auto-generated code (<#=DateTime.Now#>) + +<# +Func toUpper = s => char.ToUpper(s[0]) + s.Substring(1); + +string[] counts = new[] { "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen" }; +string[] ordinals = new[] { "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth", "tenth", "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", "sixteenth" }; + +for (int i = 2; i <= 16; i++) +{ + var parameters = string.Join(", ", Enumerable.Range(1, i).Select(j => "IObservable " + ordinals[j - 1])); + var genArgs = string.Join(", ", Enumerable.Range(1, i).Select(j => "TSource" + j)); + var sources = string.Join(", ", Enumerable.Range(1, i).Select(j => toUpper(ordinals[j - 1]))); + +#> + /// + /// Represents a join pattern over <#=counts[i - 1]#> observable sequence<#=i != 1 ? "s" : ""#>. + /// +<# +for (int j = 0; j < i; j++) +{ +#> + /// The type of the elements in the <#=ordinals[j]#> source sequence. +<# +} +#> + public class QueryablePattern<<#=genArgs#>> : QueryablePattern + { + internal QueryablePattern(Expression expression) + : base(expression) + { + } + +<# +if (i > 1 && i != 16) +{ +#> + /// + /// Creates a pattern that matches when all <#=counts[i]#> observable sequences have an available element. + /// + /// The type of the elements in the <#=ordinals[i]#> observable sequence. + /// Observable sequence to match with the <#=counts[i - 1]#> previous sequences. + /// Pattern object that matches when all observable sequences have an available element. + /// is null. + public QueryablePattern<<#=genArgs#>, TSource<#=i+1#>> And>(IObservable> other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + var t = typeof(QueryablePattern<<#=genArgs#>>); + var m = t.GetMethod(nameof(And)).MakeGenericMethod(typeof(TSource<#=i + 1#>)); + return new QueryablePattern<<#=genArgs#>, TSource<#=i + 1#>>( + Expression.Call( + Expression, + m, + Qbservable.GetSourceExpression(other) + ) + ); + } + +<# +} +#> + /// + /// Matches when all observable sequences have an available element and projects the elements by invoking the selector function. + /// + /// The type of the elements in the result sequence, returned by the selector function. + /// Selector that will be invoked for elements in the source sequences. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// is null. + public QueryablePlan Then(Expression, TResult>> selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + var t = typeof(QueryablePattern<<#=genArgs#>>); + var m = t.GetMethod(nameof(Then)).MakeGenericMethod(typeof(TResult)); + return new QueryablePlan( + Expression.Call( + Expression, + m, + selector + ) + ); + } + } + +<# +} +#> + #endregion +} diff --git a/LibExternal/System.Reactive/Joins/QueryablePlan.cs b/LibExternal/System.Reactive/Joins/QueryablePlan.cs new file mode 100644 index 0000000..06f9435 --- /dev/null +++ b/LibExternal/System.Reactive/Joins/QueryablePlan.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Linq.Expressions; + +namespace System.Reactive.Joins +{ + /// + /// Represents an execution plan for join patterns represented by an expression tree. + /// + /// The type of the results produced by the plan. + public class QueryablePlan + { + internal QueryablePlan(Expression expression) + { + Expression = expression; + } + + /// + /// Gets the expression tree representing the join pattern execution plan. + /// + public Expression Expression { get; } + } +} diff --git a/LibExternal/System.Reactive/Linq/GroupedObservable.cs b/LibExternal/System.Reactive/Linq/GroupedObservable.cs new file mode 100644 index 0000000..a802fb7 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/GroupedObservable.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; +using System.Reactive.Subjects; + +namespace System.Reactive.Linq +{ + internal sealed class GroupedObservable : ObservableBase, IGroupedObservable + { + private readonly IObservable _subject; + private readonly RefCountDisposable? _refCount; + + public GroupedObservable(TKey key, ISubject subject, RefCountDisposable refCount) + { + Key = key; + _subject = subject; + _refCount = refCount; + } + + public GroupedObservable(TKey key, ISubject subject) + { + Key = key; + _subject = subject; + } + + public TKey Key { get; } + + protected override IDisposable SubscribeCore(IObserver observer) + { + if (_refCount != null) + { + // + // [OK] Use of unsafe Subscribe: called on a known subject implementation. + // + var release = _refCount.GetDisposable(); + var subscription = _subject.Subscribe/*Unsafe*/(observer); + return StableCompositeDisposable.Create(release, subscription); + } + + // + // [OK] Use of unsafe Subscribe: called on a known subject implementation. + // + return _subject.Subscribe/*Unsafe*/(observer); + } + } +} diff --git a/LibExternal/System.Reactive/Linq/IGroupedObservable.cs b/LibExternal/System.Reactive/Linq/IGroupedObservable.cs new file mode 100644 index 0000000..c053a04 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/IGroupedObservable.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq +{ + /// + /// Represents an observable sequence of elements that have a common key. + /// + /// + /// The type of the key shared by all elements in the group. + /// This type parameter is covariant. That is, you can use either the type you specified or any type that is more derived. For more information about covariance and contravariance, see Covariance and Contravariance in Generics. + /// + /// + /// The type of the elements in the group. + /// This type parameter is covariant. That is, you can use either the type you specified or any type that is more derived. For more information about covariance and contravariance, see Covariance and Contravariance in Generics. + /// + public interface IGroupedObservable : IObservable + { + /// + /// Gets the common key. + /// + TKey Key { get; } + } +} diff --git a/LibExternal/System.Reactive/Linq/IQbservable.cs b/LibExternal/System.Reactive/Linq/IQbservable.cs new file mode 100644 index 0000000..cd2425f --- /dev/null +++ b/LibExternal/System.Reactive/Linq/IQbservable.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Linq.Expressions; + +namespace System.Reactive.Linq +{ + /// + /// Provides functionality to evaluate queries against a specific data source wherein the type of the data is known. + /// + /// + /// The type of the data in the data source. + /// This type parameter is covariant. That is, you can use either the type you specified or any type that is more derived. For more information about covariance and contravariance, see Covariance and Contravariance in Generics. + /// + [Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Qbservable", Justification = "What a pleasure to write 'by design' here.")] + public interface IQbservable : IQbservable, IObservable + { + } + + /// + /// Provides functionality to evaluate queries against a specific data source wherein the type of the data is not specified. + /// + [Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Qbservable", Justification = "What a pleasure to write 'by design' here.")] + public interface IQbservable + { + /// + /// Gets the type of the element(s) that are returned when the expression tree associated with this instance of IQbservable is executed. + /// + Type ElementType { get; } + + /// + /// Gets the expression tree that is associated with the instance of IQbservable. + /// + Expression Expression { get; } + + /// + /// Gets the query provider that is associated with this data source. + /// + IQbservableProvider Provider { get; } + } +} diff --git a/LibExternal/System.Reactive/Linq/IQbservableProvider.cs b/LibExternal/System.Reactive/Linq/IQbservableProvider.cs new file mode 100644 index 0000000..2706136 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/IQbservableProvider.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Linq.Expressions; + +namespace System.Reactive.Linq +{ + /// + /// Defines methods to create and execute queries that are described by an IQbservable object. + /// + [Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Qbservable", Justification = "What a pleasure to write 'by design' here.")] + public interface IQbservableProvider + { + /// + /// Constructs an object that can evaluate the query represented by a specified expression tree. + /// + /// The type of the elements of the that is returned. + /// Expression tree representing the query. + /// IQbservable object that can evaluate the given query expression. + IQbservable CreateQuery(Expression expression); + } +} diff --git a/LibExternal/System.Reactive/Linq/IQueryLanguage.NAry.cs b/LibExternal/System.Reactive/Linq/IQueryLanguage.NAry.cs new file mode 100644 index 0000000..af0ed6c --- /dev/null +++ b/LibExternal/System.Reactive/Linq/IQueryLanguage.NAry.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +// This code was generated by a T4 template at 05/04/2023 16:16:14. + +namespace System.Reactive.Linq +{ + internal partial interface IQueryLanguage + { + IObservable CombineLatest(IObservable source1, IObservable source2, Func resultSelector); + IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, Func resultSelector); + IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, Func resultSelector); + IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, Func resultSelector); + IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, Func resultSelector); + IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, Func resultSelector); + IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, Func resultSelector); + IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, Func resultSelector); + IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, Func resultSelector); + IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, Func resultSelector); + IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, Func resultSelector); + IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, Func resultSelector); + IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, Func resultSelector); + IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, IObservable source15, Func resultSelector); + IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, IObservable source15, IObservable source16, Func resultSelector); + + IObservable Zip(IObservable source1, IObservable source2, Func resultSelector); + IObservable Zip(IObservable source1, IObservable source2, IObservable source3, Func resultSelector); + IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, Func resultSelector); + IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, Func resultSelector); + IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, Func resultSelector); + IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, Func resultSelector); + IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, Func resultSelector); + IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, Func resultSelector); + IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, Func resultSelector); + IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, Func resultSelector); + IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, Func resultSelector); + IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, Func resultSelector); + IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, Func resultSelector); + IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, IObservable source15, Func resultSelector); + IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, IObservable source15, IObservable source16, Func resultSelector); + } + + internal partial interface IQueryLanguageEx + { + IObservable<(T1, T2)> CombineLatest(IObservable source1, IObservable source2); + IObservable<(T1, T2, T3)> CombineLatest(IObservable source1, IObservable source2, IObservable source3); + IObservable<(T1, T2, T3, T4)> CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4); + IObservable<(T1, T2, T3, T4, T5)> CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5); + IObservable<(T1, T2, T3, T4, T5, T6)> CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6); + IObservable<(T1, T2, T3, T4, T5, T6, T7)> CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7); + IObservable<(T1, T2, T3, T4, T5, T6, T7, T8)> CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8); + + IObservable<(T1, T2)> Zip(IObservable source1, IObservable source2); + IObservable<(T1, T2, T3)> Zip(IObservable source1, IObservable source2, IObservable source3); + IObservable<(T1, T2, T3, T4)> Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4); + IObservable<(T1, T2, T3, T4, T5)> Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5); + IObservable<(T1, T2, T3, T4, T5, T6)> Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6); + IObservable<(T1, T2, T3, T4, T5, T6, T7)> Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7); + IObservable<(T1, T2, T3, T4, T5, T6, T7, T8)> Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8); + } +} diff --git a/LibExternal/System.Reactive/Linq/IQueryLanguage.NAry.tt b/LibExternal/System.Reactive/Linq/IQueryLanguage.NAry.tt new file mode 100644 index 0000000..e3da85f --- /dev/null +++ b/LibExternal/System.Reactive/Linq/IQueryLanguage.NAry.tt @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> +// This code was generated by a T4 template at <#=DateTime.Now#>. + +namespace System.Reactive.Linq +{ + internal partial interface IQueryLanguage + { +<# +for (int i = 2; i <= 16; i++) +{ + var genArgs = string.Join(", ", Enumerable.Range(1, i).Select(j => "T" + j)); + var parameters = string.Join(", ", Enumerable.Range(1, i).Select(j => "IObservable source" + j)); + +#> + IObservable CombineLatest<<#=genArgs#>, TResult>(<#=parameters#>, Func<<#=genArgs#>, TResult> resultSelector); +<# +} +#> + +<# +for (int i = 2; i <= 16; i++) +{ + var genArgs = string.Join(", ", Enumerable.Range(1, i).Select(j => "T" + j)); + var parameters = string.Join(", ", Enumerable.Range(1, i).Select(j => "IObservable source" + j)); + +#> + IObservable Zip<<#=genArgs#>, TResult>(<#=parameters#>, Func<<#=genArgs#>, TResult> resultSelector); +<# +} +#> + } + + internal partial interface IQueryLanguageEx + { +<# +for (int i = 2; i <= 8; i++) +{ + var genArgs = string.Join(", ", Enumerable.Range(1, i).Select(j => "T" + j)); + var parameters = string.Join(", ", Enumerable.Range(1, i).Select(j => "IObservable source" + j)); + var tuple = "(" + string.Join(", ", Enumerable.Range(1, i).Select(j => "T" + j)) + ")"; + +#> + IObservable<<#=tuple#>> CombineLatest<<#=genArgs#>>(<#=parameters#>); +<# +} +#> + +<# +for (int i = 2; i <= 8; i++) +{ + var genArgs = string.Join(", ", Enumerable.Range(1, i).Select(j => "T" + j)); + var parameters = string.Join(", ", Enumerable.Range(1, i).Select(j => "IObservable source" + j)); + var tuple = "(" + string.Join(", ", Enumerable.Range(1, i).Select(j => "T" + j)) + ")"; + +#> + IObservable<<#=tuple#>> Zip<<#=genArgs#>>(<#=parameters#>); +<# +} +#> + } +} diff --git a/LibExternal/System.Reactive/Linq/IQueryLanguage.cs b/LibExternal/System.Reactive/Linq/IQueryLanguage.cs new file mode 100644 index 0000000..1c3f123 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/IQueryLanguage.cs @@ -0,0 +1,760 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reactive.Concurrency; +using System.Reactive.Joins; +using System.Reactive.Subjects; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Reactive.Linq +{ + /// + /// Internal interface describing the LINQ to Events query language. + /// + internal partial interface IQueryLanguage + { + #region * Aggregates * + + IObservable Aggregate(IObservable source, TAccumulate seed, Func accumulator); + IObservable Aggregate(IObservable source, TAccumulate seed, Func accumulator, Func resultSelector); + IObservable Aggregate(IObservable source, Func accumulator); + IObservable All(IObservable source, Func predicate); + IObservable Any(IObservable source); + IObservable Any(IObservable source, Func predicate); + IObservable Average(IObservable source); + IObservable Average(IObservable source); + IObservable Average(IObservable source); + IObservable Average(IObservable source); + IObservable Average(IObservable source); + IObservable Average(IObservable source); + IObservable Average(IObservable source); + IObservable Average(IObservable source); + IObservable Average(IObservable source); + IObservable Average(IObservable source); + IObservable Average(IObservable source, Func selector); + IObservable Average(IObservable source, Func selector); + IObservable Average(IObservable source, Func selector); + IObservable Average(IObservable source, Func selector); + IObservable Average(IObservable source, Func selector); + IObservable Average(IObservable source, Func selector); + IObservable Average(IObservable source, Func selector); + IObservable Average(IObservable source, Func selector); + IObservable Average(IObservable source, Func selector); + IObservable Average(IObservable source, Func selector); + IObservable Contains(IObservable source, TSource value); + IObservable Contains(IObservable source, TSource value, IEqualityComparer comparer); + IObservable Count(IObservable source); + IObservable Count(IObservable source, Func predicate); + IObservable ElementAt(IObservable source, int index); + IObservable ElementAtOrDefault(IObservable source, int index); + IObservable FirstAsync(IObservable source); + IObservable FirstAsync(IObservable source, Func predicate); + IObservable FirstOrDefaultAsync(IObservable source); + IObservable FirstOrDefaultAsync(IObservable source, Func predicate); + IObservable IsEmpty(IObservable source); + IObservable LastAsync(IObservable source); + IObservable LastAsync(IObservable source, Func predicate); + IObservable LastOrDefaultAsync(IObservable source); + IObservable LastOrDefaultAsync(IObservable source, Func predicate); + IObservable LongCount(IObservable source); + IObservable LongCount(IObservable source, Func predicate); + IObservable Max(IObservable source); + IObservable Max(IObservable source, IComparer comparer); + IObservable Max(IObservable source); + IObservable Max(IObservable source); + IObservable Max(IObservable source); + IObservable Max(IObservable source); + IObservable Max(IObservable source); + IObservable Max(IObservable source); + IObservable Max(IObservable source); + IObservable Max(IObservable source); + IObservable Max(IObservable source); + IObservable Max(IObservable source); + IObservable Max(IObservable source, Func selector); + IObservable Max(IObservable source, Func selector, IComparer comparer); + IObservable Max(IObservable source, Func selector); + IObservable Max(IObservable source, Func selector); + IObservable Max(IObservable source, Func selector); + IObservable Max(IObservable source, Func selector); + IObservable Max(IObservable source, Func selector); + IObservable Max(IObservable source, Func selector); + IObservable Max(IObservable source, Func selector); + IObservable Max(IObservable source, Func selector); + IObservable Max(IObservable source, Func selector); + IObservable Max(IObservable source, Func selector); + IObservable> MaxBy(IObservable source, Func keySelector); + IObservable> MaxBy(IObservable source, Func keySelector, IComparer comparer); + IObservable Min(IObservable source); + IObservable Min(IObservable source, IComparer comparer); + IObservable Min(IObservable source); + IObservable Min(IObservable source); + IObservable Min(IObservable source); + IObservable Min(IObservable source); + IObservable Min(IObservable source); + IObservable Min(IObservable source); + IObservable Min(IObservable source); + IObservable Min(IObservable source); + IObservable Min(IObservable source); + IObservable Min(IObservable source); + IObservable Min(IObservable source, Func selector); + IObservable Min(IObservable source, Func selector, IComparer comparer); + IObservable Min(IObservable source, Func selector); + IObservable Min(IObservable source, Func selector); + IObservable Min(IObservable source, Func selector); + IObservable Min(IObservable source, Func selector); + IObservable Min(IObservable source, Func selector); + IObservable Min(IObservable source, Func selector); + IObservable Min(IObservable source, Func selector); + IObservable Min(IObservable source, Func selector); + IObservable Min(IObservable source, Func selector); + IObservable Min(IObservable source, Func selector); + IObservable> MinBy(IObservable source, Func keySelector); + IObservable> MinBy(IObservable source, Func keySelector, IComparer comparer); + IObservable SequenceEqual(IObservable first, IObservable second); + IObservable SequenceEqual(IObservable first, IObservable second, IEqualityComparer comparer); + IObservable SequenceEqual(IObservable first, IEnumerable second); + IObservable SequenceEqual(IObservable first, IEnumerable second, IEqualityComparer comparer); + IObservable SingleAsync(IObservable source); + IObservable SingleAsync(IObservable source, Func predicate); + IObservable SingleOrDefaultAsync(IObservable source); + IObservable SingleOrDefaultAsync(IObservable source, Func predicate); + IObservable Sum(IObservable source); + IObservable Sum(IObservable source); + IObservable Sum(IObservable source); + IObservable Sum(IObservable source); + IObservable Sum(IObservable source); + IObservable Sum(IObservable source); + IObservable Sum(IObservable source); + IObservable Sum(IObservable source); + IObservable Sum(IObservable source); + IObservable Sum(IObservable source); + IObservable Sum(IObservable source, Func selector); + IObservable Sum(IObservable source, Func selector); + IObservable Sum(IObservable source, Func selector); + IObservable Sum(IObservable source, Func selector); + IObservable Sum(IObservable source, Func selector); + IObservable Sum(IObservable source, Func selector); + IObservable Sum(IObservable source, Func selector); + IObservable Sum(IObservable source, Func selector); + IObservable Sum(IObservable source, Func selector); + IObservable Sum(IObservable source, Func selector); + IObservable ToArray(IObservable source); + IObservable> ToDictionary(IObservable source, Func keySelector, Func elementSelector, IEqualityComparer comparer) where TKey : notnull; + IObservable> ToDictionary(IObservable source, Func keySelector, Func elementSelector) where TKey : notnull; + IObservable> ToDictionary(IObservable source, Func keySelector, IEqualityComparer comparer) where TKey : notnull; + IObservable> ToDictionary(IObservable source, Func keySelector) where TKey : notnull; + IObservable> ToList(IObservable source); + IObservable> ToLookup(IObservable source, Func keySelector, Func elementSelector, IEqualityComparer comparer); + IObservable> ToLookup(IObservable source, Func keySelector, IEqualityComparer comparer); + IObservable> ToLookup(IObservable source, Func keySelector, Func elementSelector); + IObservable> ToLookup(IObservable source, Func keySelector); + + #endregion + + #region * Async * + + Func> FromAsyncPattern(Func begin, Func end); + Func> FromAsyncPattern(Func begin, Func end); + Func> FromAsyncPattern(Func begin, Func end); + Func> FromAsyncPattern(Func begin, Func end); + Func> FromAsyncPattern(Func begin, Func end); + Func> FromAsyncPattern(Func begin, Func end); + Func> FromAsyncPattern(Func begin, Func end); + Func> FromAsyncPattern(Func begin, Func end); + Func> FromAsyncPattern(Func begin, Func end); + Func> FromAsyncPattern(Func begin, Func end); + Func> FromAsyncPattern(Func begin, Func end); + Func> FromAsyncPattern(Func begin, Func end); + Func> FromAsyncPattern(Func begin, Func end); + Func> FromAsyncPattern(Func begin, Func end); + Func> FromAsyncPattern(Func begin, Func end); + + Func> FromAsyncPattern(Func begin, Action end); + Func> FromAsyncPattern(Func begin, Action end); + Func> FromAsyncPattern(Func begin, Action end); + Func> FromAsyncPattern(Func begin, Action end); + Func> FromAsyncPattern(Func begin, Action end); + Func> FromAsyncPattern(Func begin, Action end); + Func> FromAsyncPattern(Func begin, Action end); + Func> FromAsyncPattern(Func begin, Action end); + Func> FromAsyncPattern(Func begin, Action end); + Func> FromAsyncPattern(Func begin, Action end); + Func> FromAsyncPattern(Func begin, Action end); + Func> FromAsyncPattern(Func begin, Action end); + Func> FromAsyncPattern(Func begin, Action end); + Func> FromAsyncPattern(Func begin, Action end); + Func> FromAsyncPattern(Func begin, Action end); + + IObservable Start(Func function); + IObservable Start(Func function, IScheduler scheduler); + + IObservable StartAsync(Func> functionAsync); + IObservable StartAsync(Func> functionAsync); + IObservable StartAsync(Func> functionAsync, in TaskObservationOptions.Value options); + IObservable StartAsync(Func> functionAsync, in TaskObservationOptions.Value options); + + IObservable Start(Action action); + IObservable Start(Action action, IScheduler scheduler); + + IObservable StartAsync(Func actionAsync); + IObservable StartAsync(Func actionAsync); + IObservable StartAsync(Func actionAsync, in TaskObservationOptions.Value options); + IObservable StartAsync(Func actionAsync, in TaskObservationOptions.Value options); + + IObservable FromAsync(Func> functionAsync); + IObservable FromAsync(Func> functionAsync); + IObservable FromAsync(Func actionAsync); + IObservable FromAsync(Func actionAsync); + IObservable FromAsync(Func> functionAsync, TaskObservationOptions.Value options); + IObservable FromAsync(Func> functionAsync, TaskObservationOptions.Value options); + IObservable FromAsync(Func actionAsync, TaskObservationOptions.Value options); + IObservable FromAsync(Func actionAsync, TaskObservationOptions.Value options); + + Func> ToAsync(Func function); + Func> ToAsync(Func function, IScheduler scheduler); + Func> ToAsync(Func function); + Func> ToAsync(Func function, IScheduler scheduler); + Func> ToAsync(Func function); + Func> ToAsync(Func function, IScheduler scheduler); + Func> ToAsync(Func function); + Func> ToAsync(Func function, IScheduler scheduler); + Func> ToAsync(Func function); + Func> ToAsync(Func function, IScheduler scheduler); + Func> ToAsync(Func function); + Func> ToAsync(Func function, IScheduler scheduler); + Func> ToAsync(Func function); + Func> ToAsync(Func function, IScheduler scheduler); + Func> ToAsync(Func function); + Func> ToAsync(Func function, IScheduler scheduler); + Func> ToAsync(Func function); + Func> ToAsync(Func function, IScheduler scheduler); + Func> ToAsync(Func function); + Func> ToAsync(Func function, IScheduler scheduler); + Func> ToAsync(Func function); + Func> ToAsync(Func function, IScheduler scheduler); + Func> ToAsync(Func function); + Func> ToAsync(Func function, IScheduler scheduler); + Func> ToAsync(Func function); + Func> ToAsync(Func function, IScheduler scheduler); + Func> ToAsync(Func function); + Func> ToAsync(Func function, IScheduler scheduler); + Func> ToAsync(Func function); + Func> ToAsync(Func function, IScheduler scheduler); + Func> ToAsync(Func function); + Func> ToAsync(Func function, IScheduler scheduler); + Func> ToAsync(Func function); + Func> ToAsync(Func function, IScheduler scheduler); + + Func> ToAsync(Action action); + Func> ToAsync(Action action, IScheduler scheduler); + Func> ToAsync(Action action); + Func> ToAsync(Action action, IScheduler scheduler); + Func> ToAsync(Action action); + Func> ToAsync(Action action, IScheduler scheduler); + Func> ToAsync(Action action); + Func> ToAsync(Action action, IScheduler scheduler); + Func> ToAsync(Action action); + Func> ToAsync(Action action, IScheduler scheduler); + Func> ToAsync(Action action); + Func> ToAsync(Action action, IScheduler scheduler); + Func> ToAsync(Action action); + Func> ToAsync(Action action, IScheduler scheduler); + Func> ToAsync(Action action); + Func> ToAsync(Action action, IScheduler scheduler); + Func> ToAsync(Action action); + Func> ToAsync(Action action, IScheduler scheduler); + Func> ToAsync(Action action); + Func> ToAsync(Action action, IScheduler scheduler); + Func> ToAsync(Action action); + Func> ToAsync(Action action, IScheduler scheduler); + Func> ToAsync(Action action); + Func> ToAsync(Action action, IScheduler scheduler); + Func> ToAsync(Action action); + Func> ToAsync(Action action, IScheduler scheduler); + Func> ToAsync(Action action); + Func> ToAsync(Action action, IScheduler scheduler); + Func> ToAsync(Action action); + Func> ToAsync(Action action, IScheduler scheduler); + Func> ToAsync(Action action); + Func> ToAsync(Action action, IScheduler scheduler); + Func> ToAsync(Action action); + Func> ToAsync(Action action, IScheduler scheduler); + + #endregion + + #region * Awaiter * + + AsyncSubject GetAwaiter(IObservable source); + AsyncSubject GetAwaiter(IConnectableObservable source); + AsyncSubject RunAsync(IObservable source, CancellationToken cancellationToken); + AsyncSubject RunAsync(IConnectableObservable source, CancellationToken cancellationToken); + + #endregion + + #region * Binding * + + IConnectableObservable Multicast(IObservable source, ISubject subject); + IObservable Multicast(IObservable source, Func> subjectSelector, Func, IObservable> selector); + IConnectableObservable Publish(IObservable source); + IObservable Publish(IObservable source, Func, IObservable> selector); + IConnectableObservable Publish(IObservable source, TSource initialValue); + IObservable Publish(IObservable source, Func, IObservable> selector, TSource initialValue); + IConnectableObservable PublishLast(IObservable source); + IObservable PublishLast(IObservable source, Func, IObservable> selector); + IObservable RefCount(IConnectableObservable source); + IObservable RefCount(IConnectableObservable source, TimeSpan disconnectDelay); + IObservable RefCount(IConnectableObservable source, TimeSpan disconnectDelay, IScheduler schedulder); + IObservable RefCount(IConnectableObservable source, int minObservers); + IObservable RefCount(IConnectableObservable source, int minObservers, TimeSpan disconnectDelay); + IObservable RefCount(IConnectableObservable source, int minObservers, TimeSpan disconnectDelay, IScheduler schedulder); + IConnectableObservable Replay(IObservable source); + IConnectableObservable Replay(IObservable source, IScheduler scheduler); + IObservable Replay(IObservable source, Func, IObservable> selector); + IObservable Replay(IObservable source, Func, IObservable> selector, IScheduler scheduler); + IConnectableObservable Replay(IObservable source, TimeSpan window); + IObservable Replay(IObservable source, Func, IObservable> selector, TimeSpan window); + IConnectableObservable Replay(IObservable source, TimeSpan window, IScheduler scheduler); + IObservable Replay(IObservable source, Func, IObservable> selector, TimeSpan window, IScheduler scheduler); + IConnectableObservable Replay(IObservable source, int bufferSize, IScheduler scheduler); + IObservable Replay(IObservable source, Func, IObservable> selector, int bufferSize, IScheduler scheduler); + IConnectableObservable Replay(IObservable source, int bufferSize); + IObservable Replay(IObservable source, Func, IObservable> selector, int bufferSize); + IConnectableObservable Replay(IObservable source, int bufferSize, TimeSpan window); + IObservable Replay(IObservable source, Func, IObservable> selector, int bufferSize, TimeSpan window); + IConnectableObservable Replay(IObservable source, int bufferSize, TimeSpan window, IScheduler scheduler); + IObservable Replay(IObservable source, Func, IObservable> selector, int bufferSize, TimeSpan window, IScheduler scheduler); + IObservable AutoConnect(IConnectableObservable source, int minObservers, Action? onConnect); + + #endregion + + #region * Blocking * + + IEnumerable> Chunkify(IObservable source); + IEnumerable Collect(IObservable source, Func newCollector, Func merge); + IEnumerable Collect(IObservable source, Func getInitialCollector, Func merge, Func getNewCollector); + TSource First(IObservable source); + TSource First(IObservable source, Func predicate); + [return: MaybeNull] TSource FirstOrDefault(IObservable source); + [return: MaybeNull] TSource FirstOrDefault(IObservable source, Func predicate); + void ForEach(IObservable source, Action onNext); + void ForEach(IObservable source, Action onNext); + IEnumerator GetEnumerator(IObservable source); + TSource Last(IObservable source); + TSource Last(IObservable source, Func predicate); + [return: MaybeNull] TSource LastOrDefault(IObservable source); + [return: MaybeNull] TSource LastOrDefault(IObservable source, Func predicate); + IEnumerable Latest(IObservable source); + IEnumerable MostRecent(IObservable source, TSource initialValue); + IEnumerable Next(IObservable source); + TSource Single(IObservable source); + [return: MaybeNull] TSource SingleOrDefault(IObservable source, Func predicate); + [return: MaybeNull] TSource SingleOrDefault(IObservable source); + TSource Single(IObservable source, Func predicate); + TSource Wait(IObservable source); + + #endregion + + #region * Concurrency * + + IObservable ObserveOn(IObservable source, IScheduler scheduler); + IObservable ObserveOn(IObservable source, SynchronizationContext context); + + IObservable SubscribeOn(IObservable source, IScheduler scheduler); + IObservable SubscribeOn(IObservable source, SynchronizationContext context); + + IObservable Synchronize(IObservable source); + IObservable Synchronize(IObservable source, object gate); + + #endregion + + #region * Conversions * + + IDisposable Subscribe(IEnumerable source, IObserver observer); + IDisposable Subscribe(IEnumerable source, IObserver observer, IScheduler scheduler); + IEnumerable ToEnumerable(IObservable source); + IEventSource ToEvent(IObservable source); + IEventSource ToEvent(IObservable source); + IEventPatternSource ToEventPattern(IObservable> source); + IObservable ToObservable(IEnumerable source); + IObservable ToObservable(IEnumerable source, IScheduler scheduler); + + #endregion + + #region * Creation * + + IObservable Create(Func, IDisposable> subscribe); + IObservable Create(Func, Action> subscribe); + IObservable Create(Func, CancellationToken, Task> subscribeAsync); + IObservable Create(Func, Task> subscribeAsync); + IObservable Create(Func, CancellationToken, Task> subscribeAsync); + IObservable Create(Func, Task> subscribeAsync); + IObservable Create(Func, CancellationToken, Task> subscribeAsync); + IObservable Create(Func, Task> subscribeAsync); + + IObservable Defer(Func> observableFactory); + + IObservable Defer(Func>> observableFactoryAsync, bool ignoreExceptionsAfterUnsubscribe); + IObservable Defer(Func>> observableFactoryAsync, bool ignoreExceptionsAfterUnsubscribe); + + IObservable Empty(); + IObservable Empty(IScheduler scheduler); + IObservable Generate(TState initialState, Func condition, Func iterate, Func resultSelector); + IObservable Generate(TState initialState, Func condition, Func iterate, Func resultSelector, IScheduler scheduler); + IObservable Never(); + IObservable Range(int start, int count); + IObservable Range(int start, int count, IScheduler scheduler); + IObservable Repeat(TResult value); + IObservable Repeat(TResult value, IScheduler scheduler); + IObservable Repeat(TResult value, int repeatCount); + IObservable Repeat(TResult value, int repeatCount, IScheduler scheduler); + IObservable Return(TResult value); + IObservable Return(TResult value, IScheduler scheduler); + IObservable Throw(Exception exception); + IObservable Throw(Exception exception, IScheduler scheduler); + IObservable Using(Func resourceFactory, Func> observableFactory) where TResource : IDisposable; + IObservable Using(Func> resourceFactoryAsync, Func>> observableFactoryAsync) where TResource : IDisposable; + + #endregion + + #region * Events * + + IObservable> FromEventPattern(Action addHandler, Action removeHandler); + IObservable> FromEventPattern(Action addHandler, Action removeHandler, IScheduler scheduler); + IObservable> FromEventPattern(Action addHandler, Action removeHandler); + IObservable> FromEventPattern(Action addHandler, Action removeHandler, IScheduler scheduler); + IObservable> FromEventPattern(Func, TDelegate> conversion, Action addHandler, Action removeHandler); + IObservable> FromEventPattern(Func, TDelegate> conversion, Action addHandler, Action removeHandler, IScheduler scheduler); + IObservable> FromEventPattern(Action> addHandler, Action> removeHandler); + IObservable> FromEventPattern(Action> addHandler, Action> removeHandler, IScheduler scheduler); + IObservable> FromEventPattern(Action addHandler, Action removeHandler); + IObservable> FromEventPattern(Action addHandler, Action removeHandler, IScheduler scheduler); +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + IObservable> FromEventPattern(object target, string eventName); +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + IObservable> FromEventPattern(object target, string eventName, IScheduler scheduler); +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + IObservable> FromEventPattern(object target, string eventName); +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + IObservable> FromEventPattern(object target, string eventName, IScheduler scheduler); +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + IObservable> FromEventPattern(object target, string eventName); +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + IObservable> FromEventPattern(object target, string eventName, IScheduler scheduler); +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + IObservable> FromEventPattern(Type type, string eventName); +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + IObservable> FromEventPattern(Type type, string eventName, IScheduler scheduler); +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + IObservable> FromEventPattern(Type type, string eventName); +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + IObservable> FromEventPattern(Type type, string eventName, IScheduler scheduler); +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + IObservable> FromEventPattern(Type type, string eventName); +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + IObservable> FromEventPattern(Type type, string eventName, IScheduler scheduler); + + IObservable FromEvent(Func, TDelegate> conversion, Action addHandler, Action removeHandler); + IObservable FromEvent(Func, TDelegate> conversion, Action addHandler, Action removeHandler, IScheduler scheduler); + IObservable FromEvent(Action addHandler, Action removeHandler); + IObservable FromEvent(Action addHandler, Action removeHandler, IScheduler scheduler); + IObservable FromEvent(Action> addHandler, Action> removeHandler); + IObservable FromEvent(Action> addHandler, Action> removeHandler, IScheduler scheduler); + IObservable FromEvent(Action addHandler, Action removeHandler); + IObservable FromEvent(Action addHandler, Action removeHandler, IScheduler scheduler); + + #endregion + + #region * Imperative * + + Task ForEachAsync(IObservable source, Action onNext); + Task ForEachAsync(IObservable source, Action onNext, CancellationToken cancellationToken); + Task ForEachAsync(IObservable source, Action onNext); + Task ForEachAsync(IObservable source, Action onNext, CancellationToken cancellationToken); + + IObservable Case(Func selector, IDictionary> sources, IObservable defaultSource) where TValue : notnull; + IObservable Case(Func selector, IDictionary> sources, IScheduler scheduler) where TValue : notnull; + IObservable Case(Func selector, IDictionary> sources) where TValue : notnull; + IObservable DoWhile(IObservable source, Func condition); + IObservable For(IEnumerable source, Func> resultSelector); + IObservable If(Func condition, IObservable thenSource, IObservable elseSource); + IObservable If(Func condition, IObservable thenSource); + IObservable If(Func condition, IObservable thenSource, IScheduler scheduler); + IObservable While(Func condition, IObservable source); + + #endregion + + #region * Joins * + + Pattern And(IObservable left, IObservable right); + Plan Then(IObservable source, Func selector); + IObservable When(params Plan[] plans); + IObservable When(IEnumerable> plans); + + #endregion + + #region * Multiple * + + IObservable Amb(IObservable first, IObservable second); + IObservable Amb(params IObservable[] sources); + IObservable Amb(IEnumerable> sources); + IObservable> Buffer(IObservable source, Func> bufferClosingSelector); + IObservable> Buffer(IObservable source, IObservable bufferOpenings, Func> bufferClosingSelector); + IObservable> Buffer(IObservable source, IObservable bufferBoundaries); + IObservable Catch(IObservable source, Func> handler) where TException : Exception; + IObservable Catch(IObservable first, IObservable second); + IObservable Catch(params IObservable[] sources); + IObservable Catch(IEnumerable> sources); + // NB: N-ary overloads of CombineLatest are generated in IQueryLanguage.tt. + IObservable CombineLatest(IEnumerable> sources, Func, TResult> resultSelector); + IObservable> CombineLatest(IEnumerable> sources); + IObservable> CombineLatest(params IObservable[] sources); + IObservable Concat(IObservable first, IObservable second); + IObservable Concat(params IObservable[] sources); + IObservable Concat(IEnumerable> sources); + IObservable Concat(IObservable> sources); + IObservable Merge(IObservable> sources); + IObservable Merge(IObservable> sources, int maxConcurrent); + IObservable Merge(IEnumerable> sources, int maxConcurrent); + IObservable Merge(IEnumerable> sources, int maxConcurrent, IScheduler scheduler); + IObservable Merge(IObservable first, IObservable second); + IObservable Merge(IObservable first, IObservable second, IScheduler scheduler); + IObservable Merge(params IObservable[] sources); + IObservable Merge(IScheduler scheduler, params IObservable[] sources); + IObservable Merge(IEnumerable> sources); + IObservable Merge(IEnumerable> sources, IScheduler scheduler); + IObservable OnErrorResumeNext(IObservable first, IObservable second); + IObservable OnErrorResumeNext(params IObservable[] sources); + IObservable OnErrorResumeNext(IEnumerable> sources); + IObservable SkipUntil(IObservable source, IObservable other); + IObservable Switch(IObservable> sources); + IObservable TakeUntil(IObservable source, IObservable other); + IObservable TakeUntil(IObservable source, Func stopPredicate); + IObservable> Window(IObservable source, Func> windowClosingSelector); + IObservable> Window(IObservable source, IObservable windowOpenings, Func> windowClosingSelector); + IObservable> Window(IObservable source, IObservable windowBoundaries); + IObservable WithLatestFrom(IObservable first, IObservable second, Func resultSelector); + IObservable Zip(IEnumerable> sources, Func, TResult> resultSelector); + IObservable> Zip(IEnumerable> sources); + IObservable> Zip(params IObservable[] sources); + // NB: N-ary overloads of Zip are generated in IQueryLanguage.tt. + IObservable Zip(IObservable first, IEnumerable second, Func resultSelector); + + IObservable Concat(IObservable> sources); + IObservable Merge(IObservable> sources); + IObservable Switch(IObservable> sources); + + #endregion + + #region * Single * + + IObservable Append(IObservable source, TSource value); + IObservable Append(IObservable source, TSource value, IScheduler scheduler); + IObservable AsObservable(IObservable source); + IObservable> Buffer(IObservable source, int count); + IObservable> Buffer(IObservable source, int count, int skip); + IObservable Dematerialize(IObservable> source); + IObservable DistinctUntilChanged(IObservable source); + IObservable DistinctUntilChanged(IObservable source, IEqualityComparer comparer); + IObservable DistinctUntilChanged(IObservable source, Func keySelector); + IObservable DistinctUntilChanged(IObservable source, Func keySelector, IEqualityComparer comparer); + IObservable Do(IObservable source, Action onNext); + IObservable Do(IObservable source, Action onNext, Action onCompleted); + IObservable Do(IObservable source, Action onNext, Action onError); + IObservable Do(IObservable source, Action onNext, Action onError, Action onCompleted); + IObservable Do(IObservable source, IObserver observer); + IObservable Finally(IObservable source, Action finallyAction); + IObservable IgnoreElements(IObservable source); + IObservable> Materialize(IObservable source); + IObservable Prepend(IObservable source, TSource value); + IObservable Prepend(IObservable source, TSource value, IScheduler scheduler); + IObservable Repeat(IObservable source); + IObservable Repeat(IObservable source, int repeatCount); + IObservable RepeatWhen(IObservable source, Func, IObservable> handler); + IObservable Retry(IObservable source); + IObservable Retry(IObservable source, int retryCount); + IObservable RetryWhen(IObservable source, Func, IObservable> handler); + IObservable Scan(IObservable source, TAccumulate seed, Func accumulator); + IObservable Scan(IObservable source, Func accumulator); + IObservable SkipLast(IObservable source, int count); + IObservable StartWith(IObservable source, params TSource[] values); + IObservable StartWith(IObservable source, IScheduler scheduler, params TSource[] values); + IObservable StartWith(IObservable source, IEnumerable values); + IObservable StartWith(IObservable source, IScheduler scheduler, IEnumerable values); + IObservable TakeLast(IObservable source, int count); + IObservable TakeLast(IObservable source, int count, IScheduler scheduler); + IObservable> TakeLastBuffer(IObservable source, int count); + IObservable> Window(IObservable source, int count, int skip); + IObservable> Window(IObservable source, int count); + + #endregion + + #region * StandardSequenceOperators * + + IObservable Cast(IObservable source); + IObservable DefaultIfEmpty(IObservable source); + IObservable DefaultIfEmpty(IObservable source, TSource defaultValue); + IObservable Distinct(IObservable source); + IObservable Distinct(IObservable source, IEqualityComparer comparer); + IObservable Distinct(IObservable source, Func keySelector); + IObservable Distinct(IObservable source, Func keySelector, IEqualityComparer comparer); + IObservable> GroupBy(IObservable source, Func keySelector, Func elementSelector); + IObservable> GroupBy(IObservable source, Func keySelector, IEqualityComparer comparer); + IObservable> GroupBy(IObservable source, Func keySelector); + IObservable> GroupBy(IObservable source, Func keySelector, Func elementSelector, IEqualityComparer comparer); + IObservable> GroupBy(IObservable source, Func keySelector, Func elementSelector, int capacity); + IObservable> GroupBy(IObservable source, Func keySelector, int capacity, IEqualityComparer comparer); + IObservable> GroupBy(IObservable source, Func keySelector, int capacity); + IObservable> GroupBy(IObservable source, Func keySelector, Func elementSelector, int capacity, IEqualityComparer comparer); + IObservable> GroupByUntil(IObservable source, Func keySelector, Func elementSelector, Func, IObservable> durationSelector, IEqualityComparer comparer); + IObservable> GroupByUntil(IObservable source, Func keySelector, Func elementSelector, Func, IObservable> durationSelector); + IObservable> GroupByUntil(IObservable source, Func keySelector, Func, IObservable> durationSelector, IEqualityComparer comparer); + IObservable> GroupByUntil(IObservable source, Func keySelector, Func, IObservable> durationSelector); + IObservable> GroupByUntil(IObservable source, Func keySelector, Func elementSelector, Func, IObservable> durationSelector, int capacity, IEqualityComparer comparer); + IObservable> GroupByUntil(IObservable source, Func keySelector, Func elementSelector, Func, IObservable> durationSelector, int capacity); + IObservable> GroupByUntil(IObservable source, Func keySelector, Func, IObservable> durationSelector, int capacity, IEqualityComparer comparer); + IObservable> GroupByUntil(IObservable source, Func keySelector, Func, IObservable> durationSelector, int capacity); + IObservable GroupJoin(IObservable left, IObservable right, Func> leftDurationSelector, Func> rightDurationSelector, Func, TResult> resultSelector); + IObservable Join(IObservable left, IObservable right, Func> leftDurationSelector, Func> rightDurationSelector, Func resultSelector); + IObservable OfType(IObservable source); + IObservable Select(IObservable source, Func selector); + IObservable Select(IObservable source, Func selector); + IObservable SelectMany(IObservable source, IObservable other); + IObservable SelectMany(IObservable source, Func> selector); + IObservable SelectMany(IObservable source, Func> selector); + IObservable SelectMany(IObservable source, Func> collectionSelector, Func resultSelector); + IObservable SelectMany(IObservable source, Func> collectionSelector, Func resultSelector); + IObservable SelectMany(IObservable source, Func> onNext, Func> onError, Func> onCompleted); + IObservable SelectMany(IObservable source, Func> onNext, Func> onError, Func> onCompleted); + IObservable SelectMany(IObservable source, Func> selector); + IObservable SelectMany(IObservable source, Func> selector); + IObservable SelectMany(IObservable source, Func> collectionSelector, Func resultSelector); + IObservable SelectMany(IObservable source, Func> collectionSelector, Func resultSelector); + IObservable Skip(IObservable source, int count); + IObservable SkipWhile(IObservable source, Func predicate); + IObservable SkipWhile(IObservable source, Func predicate); + IObservable Take(IObservable source, int count); + IObservable Take(IObservable source, int count, IScheduler scheduler); + IObservable TakeWhile(IObservable source, Func predicate); + IObservable TakeWhile(IObservable source, Func predicate); + IObservable Where(IObservable source, Func predicate); + IObservable Where(IObservable source, Func predicate); + + IObservable SelectMany(IObservable source, Func> selector); + IObservable SelectMany(IObservable source, Func> selector); + IObservable SelectMany(IObservable source, Func> selector); + IObservable SelectMany(IObservable source, Func> selector); + IObservable SelectMany(IObservable source, Func> taskSelector, Func resultSelector); + IObservable SelectMany(IObservable source, Func> taskSelector, Func resultSelector); + IObservable SelectMany(IObservable source, Func> taskSelector, Func resultSelector); + IObservable SelectMany(IObservable source, Func> taskSelector, Func resultSelector); + + #endregion + + #region * Time * + + IObservable> Buffer(IObservable source, TimeSpan timeSpan); + IObservable> Buffer(IObservable source, TimeSpan timeSpan, IScheduler scheduler); + IObservable> Buffer(IObservable source, TimeSpan timeSpan, TimeSpan timeShift); + IObservable> Buffer(IObservable source, TimeSpan timeSpan, TimeSpan timeShift, IScheduler scheduler); + IObservable> Buffer(IObservable source, TimeSpan timeSpan, int count); + IObservable> Buffer(IObservable source, TimeSpan timeSpan, int count, IScheduler scheduler); + IObservable Delay(IObservable source, TimeSpan dueTime); + IObservable Delay(IObservable source, TimeSpan dueTime, IScheduler scheduler); + IObservable Delay(IObservable source, DateTimeOffset dueTime); + IObservable Delay(IObservable source, DateTimeOffset dueTime, IScheduler scheduler); + IObservable Delay(IObservable source, Func> delayDurationSelector); + IObservable Delay(IObservable source, IObservable subscriptionDelay, Func> delayDurationSelector); + IObservable DelaySubscription(IObservable source, TimeSpan dueTime); + IObservable DelaySubscription(IObservable source, TimeSpan dueTime, IScheduler scheduler); + IObservable DelaySubscription(IObservable source, DateTimeOffset dueTime); + IObservable DelaySubscription(IObservable source, DateTimeOffset dueTime, IScheduler scheduler); + IObservable Generate(TState initialState, Func condition, Func iterate, Func resultSelector, Func timeSelector); + IObservable Generate(TState initialState, Func condition, Func iterate, Func resultSelector, Func timeSelector, IScheduler scheduler); + IObservable Generate(TState initialState, Func condition, Func iterate, Func resultSelector, Func timeSelector); + IObservable Generate(TState initialState, Func condition, Func iterate, Func resultSelector, Func timeSelector, IScheduler scheduler); + IObservable Interval(TimeSpan period); + IObservable Interval(TimeSpan period, IScheduler scheduler); + IObservable Sample(IObservable source, TimeSpan interval); + IObservable Sample(IObservable source, TimeSpan interval, IScheduler scheduler); + IObservable Sample(IObservable source, IObservable sampler); + IObservable Skip(IObservable source, TimeSpan duration); + IObservable Skip(IObservable source, TimeSpan duration, IScheduler scheduler); + IObservable SkipLast(IObservable source, TimeSpan duration); + IObservable SkipLast(IObservable source, TimeSpan duration, IScheduler scheduler); + IObservable SkipUntil(IObservable source, DateTimeOffset startTime); + IObservable SkipUntil(IObservable source, DateTimeOffset startTime, IScheduler scheduler); + IObservable Take(IObservable source, TimeSpan duration); + IObservable Take(IObservable source, TimeSpan duration, IScheduler scheduler); + IObservable TakeLast(IObservable source, TimeSpan duration); + IObservable TakeLast(IObservable source, TimeSpan duration, IScheduler scheduler); + IObservable TakeLast(IObservable source, TimeSpan duration, IScheduler timerScheduler, IScheduler loopScheduler); + IObservable> TakeLastBuffer(IObservable source, TimeSpan duration); + IObservable> TakeLastBuffer(IObservable source, TimeSpan duration, IScheduler scheduler); + IObservable TakeUntil(IObservable source, DateTimeOffset endTime); + IObservable TakeUntil(IObservable source, DateTimeOffset endTime, IScheduler scheduler); + IObservable Throttle(IObservable source, TimeSpan dueTime); + IObservable Throttle(IObservable source, TimeSpan dueTime, IScheduler scheduler); + IObservable Throttle(IObservable source, Func> throttleDurationSelector); + IObservable> TimeInterval(IObservable source); + IObservable> TimeInterval(IObservable source, IScheduler scheduler); + IObservable Timeout(IObservable source, TimeSpan dueTime); + IObservable Timeout(IObservable source, TimeSpan dueTime, IScheduler scheduler); + IObservable Timeout(IObservable source, TimeSpan dueTime, IObservable other); + IObservable Timeout(IObservable source, TimeSpan dueTime, IObservable other, IScheduler scheduler); + IObservable Timeout(IObservable source, DateTimeOffset dueTime); + IObservable Timeout(IObservable source, DateTimeOffset dueTime, IScheduler scheduler); + IObservable Timeout(IObservable source, DateTimeOffset dueTime, IObservable other); + IObservable Timeout(IObservable source, DateTimeOffset dueTime, IObservable other, IScheduler scheduler); + IObservable Timeout(IObservable source, Func> timeoutDurationSelector); + IObservable Timeout(IObservable source, Func> timeoutDurationSelector, IObservable other); + IObservable Timeout(IObservable source, IObservable firstTimeout, Func> timeoutDurationSelector); + IObservable Timeout(IObservable source, IObservable firstTimeout, Func> timeoutDurationSelector, IObservable other); + IObservable Timer(TimeSpan dueTime); + IObservable Timer(DateTimeOffset dueTime); + IObservable Timer(TimeSpan dueTime, TimeSpan period); + IObservable Timer(DateTimeOffset dueTime, TimeSpan period); + IObservable Timer(TimeSpan dueTime, IScheduler scheduler); + IObservable Timer(DateTimeOffset dueTime, IScheduler scheduler); + IObservable Timer(TimeSpan dueTime, TimeSpan period, IScheduler scheduler); + IObservable Timer(DateTimeOffset dueTime, TimeSpan period, IScheduler scheduler); + IObservable> Timestamp(IObservable source); + IObservable> Timestamp(IObservable source, IScheduler scheduler); + IObservable> Window(IObservable source, TimeSpan timeSpan); + IObservable> Window(IObservable source, TimeSpan timeSpan, IScheduler scheduler); + IObservable> Window(IObservable source, TimeSpan timeSpan, TimeSpan timeShift); + IObservable> Window(IObservable source, TimeSpan timeSpan, TimeSpan timeShift, IScheduler scheduler); + IObservable> Window(IObservable source, TimeSpan timeSpan, int count); + IObservable> Window(IObservable source, TimeSpan timeSpan, int count, IScheduler scheduler); + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/IQueryLanguageEx.cs b/LibExternal/System.Reactive/Linq/IQueryLanguageEx.cs new file mode 100644 index 0000000..100068a --- /dev/null +++ b/LibExternal/System.Reactive/Linq/IQueryLanguageEx.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Concurrency; + +namespace System.Reactive.Linq +{ + /// + /// Internal interface describing the LINQ to Events query language. + /// + internal partial interface IQueryLanguageEx + { + IObservable Create(Func, IEnumerable>> iteratorMethod); + IObservable Create(Func>> iteratorMethod); + + IObservable Expand(IObservable source, Func> selector); + IObservable Expand(IObservable source, Func> selector, IScheduler scheduler); + + IObservable ForkJoin(IObservable first, IObservable second, Func resultSelector); + IObservable ForkJoin(params IObservable[] sources); + IObservable ForkJoin(IEnumerable> sources); + + IObservable Let(IObservable source, Func, IObservable> function); + + IObservable ManySelect(IObservable source, Func, TResult> selector); + IObservable ManySelect(IObservable source, Func, TResult> selector, IScheduler scheduler); + + ListObservable ToListObservable(IObservable source); + + IObservable<(TFirst First, TSecond Second)> WithLatestFrom(IObservable first, IObservable second); + + IObservable<(TFirst First, TSecond Second)> Zip(IObservable first, IEnumerable second); + } +} diff --git a/LibExternal/System.Reactive/Linq/LocalQueryMethodImplementationTypeAttribute.cs b/LibExternal/System.Reactive/Linq/LocalQueryMethodImplementationTypeAttribute.cs new file mode 100644 index 0000000..2259e38 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/LocalQueryMethodImplementationTypeAttribute.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; + +namespace System.Reactive.Linq +{ + /// + /// Attribute applied to static classes providing expression tree forms of query methods, + /// mapping those to the corresponding methods for local query execution on the specified + /// target class type. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public sealed class LocalQueryMethodImplementationTypeAttribute : Attribute + { + private readonly Type _targetType; + + /// + /// Creates a new mapping to the specified local execution query method implementation type. + /// + /// Type with query methods for local execution. + public LocalQueryMethodImplementationTypeAttribute(Type targetType) + { + _targetType = targetType; + } + + /// + /// Gets the type with the implementation of local query methods. + /// + public Type TargetType + { + get { return _targetType; } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable.Aggregates.cs b/LibExternal/System.Reactive/Linq/Observable.Aggregates.cs new file mode 100644 index 0000000..c901689 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable.Aggregates.cs @@ -0,0 +1,3105 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; + +namespace System.Reactive.Linq +{ + public static partial class Observable + { + #region + Aggregate + + + /// + /// Applies an accumulator function over an observable sequence, returning the result of the aggregation as a single element in the result sequence. The specified seed value is used as the initial accumulator value. + /// For aggregation behavior with incremental intermediate results, see . + /// + /// The type of the elements in the source sequence. + /// The type of the result of the aggregation. + /// An observable sequence to aggregate over. + /// The initial accumulator value. + /// An accumulator function to be invoked on each element. + /// An observable sequence containing a single element with the final accumulator value. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Aggregate(this IObservable source, TAccumulate seed, Func accumulator) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (accumulator == null) + { + throw new ArgumentNullException(nameof(accumulator)); + } + + return s_impl.Aggregate(source, seed, accumulator); + } + + /// + /// Applies an accumulator function over an observable sequence, returning the result of the aggregation as a single element in the result sequence. The specified seed value is used as the initial accumulator value, + /// and the specified result selector function is used to select the result value. + /// + /// The type of the elements in the source sequence. + /// The type of the accumulator value. + /// The type of the resulting value. + /// An observable sequence to aggregate over. + /// The initial accumulator value. + /// An accumulator function to be invoked on each element. + /// A function to transform the final accumulator value into the result value. + /// An observable sequence containing a single element with the final accumulator value. + /// or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Aggregate(this IObservable source, TAccumulate seed, Func accumulator, Func resultSelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (accumulator == null) + { + throw new ArgumentNullException(nameof(accumulator)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.Aggregate(source, seed, accumulator, resultSelector); + } + + /// + /// Applies an accumulator function over an observable sequence, returning the result of the aggregation as a single element in the result sequence. + /// For aggregation behavior with incremental intermediate results, see . + /// + /// The type of the elements in the source sequence and the result of the aggregation. + /// An observable sequence to aggregate over. + /// An accumulator function to be invoked on each element. + /// An observable sequence containing a single element with the final accumulator value. + /// or is null. + /// (Asynchronous) The source sequence is empty. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Aggregate(this IObservable source, Func accumulator) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (accumulator == null) + { + throw new ArgumentNullException(nameof(accumulator)); + } + + return s_impl.Aggregate(source, accumulator); + } + + #endregion + + #region + All + + + /// + /// Determines whether all elements of an observable sequence satisfy a condition. + /// + /// The type of the elements in the source sequence. + /// An observable sequence whose elements to apply the predicate to. + /// A function to test each element for a condition. + /// An observable sequence containing a single element determining whether all elements in the source sequence pass the test in the specified predicate. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable All(this IObservable source, Func predicate) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return s_impl.All(source, predicate); + } + + #endregion + + #region + Any + + + /// + /// Determines whether an observable sequence contains any elements. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to check for non-emptiness. + /// An observable sequence containing a single element determining whether the source sequence contains any elements. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Any(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Any(source); + } + + /// + /// Determines whether any element of an observable sequence satisfies a condition. + /// + /// The type of the elements in the source sequence. + /// An observable sequence whose elements to apply the predicate to. + /// A function to test each element for a condition. + /// An observable sequence containing a single element determining whether any elements in the source sequence pass the test in the specified predicate. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Any(this IObservable source, Func predicate) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return s_impl.Any(source, predicate); + } + + #endregion + + #region + Average + + + /// + /// Computes the average of an observable sequence of values. + /// + /// A sequence of values to calculate the average of. + /// An observable sequence containing a single element with the average of the sequence of values. + /// is null. + /// (Asynchronous) The source sequence is empty. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Average(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Average(source); + } + + /// + /// Computes the average of an observable sequence of values. + /// + /// A sequence of values to calculate the average of. + /// An observable sequence containing a single element with the average of the sequence of values. + /// is null. + /// (Asynchronous) The source sequence is empty. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Average(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Average(source); + } + + /// + /// Computes the average of an observable sequence of values. + /// + /// A sequence of values to calculate the average of. + /// An observable sequence containing a single element with the average of the sequence of values. + /// is null. + /// (Asynchronous) The source sequence is empty. + /// (Asynchronous) The sum of the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Average(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Average(source); + } + + /// + /// Computes the average of an observable sequence of values. + /// + /// A sequence of values to calculate the average of. + /// An observable sequence containing a single element with the average of the sequence of values. + /// is null. + /// (Asynchronous) The source sequence is empty. + /// (Asynchronous) The sum of the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Average(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Average(source); + } + + /// + /// Computes the average of an observable sequence of values. + /// + /// A sequence of values to calculate the average of. + /// An observable sequence containing a single element with the average of the sequence of values. + /// is null. + /// (Asynchronous) The source sequence is empty. + /// (Asynchronous) The sum of the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Average(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Average(source); + } + + /// + /// Computes the average of an observable sequence of nullable values. + /// + /// A sequence of nullable values to calculate the average of. + /// An observable sequence containing a single element with the average of the sequence of values, or null if the source sequence is empty or contains only values that are null. + /// is null. + /// (Asynchronous) The source sequence is empty. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Average(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Average(source); + } + + /// + /// Computes the average of an observable sequence of nullable values. + /// + /// A sequence of nullable values to calculate the average of. + /// An observable sequence containing a single element with the average of the sequence of values, or null if the source sequence is empty or contains only values that are null. + /// is null. + /// (Asynchronous) The source sequence is empty. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Average(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Average(source); + } + + /// + /// Computes the average of an observable sequence of nullable values. + /// + /// A sequence of nullable values to calculate the average of. + /// An observable sequence containing a single element with the average of the sequence of values, or null if the source sequence is empty or contains only values that are null. + /// is null. + /// (Asynchronous) The source sequence is empty. + /// (Asynchronous) The sum of the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Average(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Average(source); + } + + /// + /// Computes the average of an observable sequence of nullable values. + /// + /// A sequence of nullable values to calculate the average of. + /// An observable sequence containing a single element with the average of the sequence of values, or null if the source sequence is empty or contains only values that are null. + /// is null. + /// (Asynchronous) The source sequence is empty. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + /// (Asynchronous) The sum of the elements in the source sequence is larger than . + public static IObservable Average(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Average(source); + } + + /// + /// Computes the average of an observable sequence of nullable values. + /// + /// A sequence of nullable values to calculate the average of. + /// An observable sequence containing a single element with the average of the sequence of values, or null if the source sequence is empty or contains only values that are null. + /// is null. + /// (Asynchronous) The source sequence is empty. + /// (Asynchronous) The sum of the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Average(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Average(source); + } + + /// + /// Computes the average of an observable sequence of values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to calculate the average of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the average of the sequence of values. + /// or is null. + /// (Asynchronous) The source sequence is empty. + /// (Asynchronous) The sum of the projected values for the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Average(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Average(source, selector); + } + + /// + /// Computes the average of an observable sequence of values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to calculate the average of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the average of the sequence of values. + /// or is null. + /// (Asynchronous) The source sequence is empty. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Average(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Average(source, selector); + } + + /// + /// Computes the average of an observable sequence of values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to calculate the average of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the average of the sequence of values. + /// or is null. + /// (Asynchronous) The source sequence is empty. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Average(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Average(source, selector); + } + + /// + /// Computes the average of an observable sequence of values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to calculate the average of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the average of the sequence of values. + /// or is null. + /// (Asynchronous) The source sequence is empty. + /// (Asynchronous) The sum of the projected values for the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Average(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Average(source, selector); + } + + /// + /// Computes the average of an observable sequence of values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to calculate the average of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the average of the sequence of values. + /// or is null. + /// (Asynchronous) The source sequence is empty. + /// (Asynchronous) The sum of the projected values for the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Average(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Average(source, selector); + } + + /// + /// Computes the average of an observable sequence of nullable values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to calculate the average of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the average of the sequence of values, or null if the source sequence is empty or contains only values that are null. + /// or is null. + /// (Asynchronous) The source sequence is empty. + /// (Asynchronous) The sum of the projected values for the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Average(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Average(source, selector); + } + + /// + /// Computes the average of an observable sequence of nullable values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to calculate the average of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the average of the sequence of values, or null if the source sequence is empty or contains only values that are null. + /// or is null. + /// (Asynchronous) The source sequence is empty. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Average(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Average(source, selector); + } + + /// + /// Computes the average of an observable sequence of nullable values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to calculate the average of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the average of the sequence of values, or null if the source sequence is empty or contains only values that are null. + /// or is null. + /// (Asynchronous) The source sequence is empty. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Average(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Average(source, selector); + } + + /// + /// Computes the average of an observable sequence of nullable values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to calculate the average of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the average of the sequence of values, or null if the source sequence is empty or contains only values that are null. + /// or is null. + /// (Asynchronous) The source sequence is empty. + /// (Asynchronous) The sum of the projected values for the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Average(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Average(source, selector); + } + + /// + /// Computes the average of an observable sequence of nullable values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to calculate the average of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the average of the sequence of values, or null if the source sequence is empty or contains only values that are null. + /// or is null. + /// (Asynchronous) The source sequence is empty. + /// (Asynchronous) The sum of the projected values for the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Average(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Average(source, selector); + } + + #endregion + + #region + Contains + + + /// + /// Determines whether an observable sequence contains a specified element by using the default equality comparer. + /// + /// The type of the elements in the source sequence. + /// An observable sequence in which to locate a value. + /// The value to locate in the source sequence. + /// An observable sequence containing a single element determining whether the source sequence contains an element that has the specified value. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Contains(this IObservable source, TSource value) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Contains(source, value); + } + + /// + /// Determines whether an observable sequence contains a specified element by using a specified System.Collections.Generic.IEqualityComparer{T}. + /// + /// The type of the elements in the source sequence. + /// An observable sequence in which to locate a value. + /// The value to locate in the source sequence. + /// An equality comparer to compare elements. + /// An observable sequence containing a single element determining whether the source sequence contains an element that has the specified value. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Contains(this IObservable source, TSource value, IEqualityComparer comparer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.Contains(source, value, comparer); + } + + #endregion + + #region + Count + + + /// + /// Returns an observable sequence containing an that represents the total number of elements in an observable sequence. + /// + /// The type of the elements in the source sequence. + /// An observable sequence that contains elements to be counted. + /// An observable sequence containing a single element with the number of elements in the input sequence. + /// is null. + /// (Asynchronous) The number of elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Count(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Count(source); + } + + /// + /// Returns an observable sequence containing an that represents how many elements in the specified observable sequence satisfy a condition. + /// + /// The type of the elements in the source sequence. + /// An observable sequence that contains elements to be counted. + /// A function to test each element for a condition. + /// An observable sequence containing a single element with a number that represents how many elements in the input sequence satisfy the condition in the predicate function. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Count(this IObservable source, Func predicate) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return s_impl.Count(source, predicate); + } + + #endregion + + #region + ElementAt + + + /// + /// Returns the element at a specified index in a sequence. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to return the element from. + /// The zero-based index of the element to retrieve. + /// An observable sequence that produces the element at the specified position in the source sequence. + /// is null. + /// is less than zero. + /// (Asynchronous) is greater than or equal to the number of elements in the source sequence. + public static IObservable ElementAt(this IObservable source, int index) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return s_impl.ElementAt(source, index); + } + + #endregion + + #region + ElementAtOrDefault + + + /// + /// Returns the element at a specified index in a sequence or a default value if the index is out of range. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to return the element from. + /// The zero-based index of the element to retrieve. + /// An observable sequence that produces the element at the specified position in the source sequence, or a default value if the index is outside the bounds of the source sequence. + /// is null. + /// is less than zero. + public static IObservable ElementAtOrDefault(this IObservable source, int index) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return s_impl.ElementAtOrDefault(source, index); + } + + #endregion + + #region + FirstAsync + + + /// + /// Returns the first element of an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// Sequence containing the first element in the observable sequence. + /// is null. + /// (Asynchronous) The source sequence is empty. + public static IObservable FirstAsync(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.FirstAsync(source); + } + + /// + /// Returns the first element of an observable sequence that satisfies the condition in the predicate. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// A predicate function to evaluate for elements in the source sequence. + /// Sequence containing the first element in the observable sequence that satisfies the condition in the predicate. + /// or is null. + /// (Asynchronous) No element satisfies the condition in the predicate. -or- The source sequence is empty. + public static IObservable FirstAsync(this IObservable source, Func predicate) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return s_impl.FirstAsync(source, predicate); + } + + #endregion + + #region + FirstOrDefaultAsync + + + /// + /// Returns the first element of an observable sequence, or a default value if no such element exists. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// Sequence containing the first element in the observable sequence, or a default value if no such element exists. + /// is null. + public static IObservable FirstOrDefaultAsync(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.FirstOrDefaultAsync(source); + } + + /// + /// Returns the first element of an observable sequence that satisfies the condition in the predicate, or a default value if no such element exists. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// A predicate function to evaluate for elements in the source sequence. + /// Sequence containing the first element in the observable sequence that satisfies the condition in the predicate, or a default value if no such element exists. + /// or is null. + public static IObservable FirstOrDefaultAsync(this IObservable source, Func predicate) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return s_impl.FirstOrDefaultAsync(source, predicate); + } + + #endregion + + #region + IsEmpty + + + /// + /// Determines whether an observable sequence is empty. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to check for emptiness. + /// An observable sequence containing a single element determining whether the source sequence is empty. + /// is null. + public static IObservable IsEmpty(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.IsEmpty(source); + } + + #endregion + + #region + LastAsync + + + /// + /// Returns the last element of an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// Sequence containing the last element in the observable sequence. + /// is null. + /// (Asynchronous) The source sequence is empty. + public static IObservable LastAsync(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.LastAsync(source); + } + + /// + /// Returns the last element of an observable sequence that satisfies the condition in the predicate. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// A predicate function to evaluate for elements in the source sequence. + /// Sequence containing the last element in the observable sequence that satisfies the condition in the predicate. + /// or is null. + /// (Asynchronous) No element satisfies the condition in the predicate. -or- The source sequence is empty. + public static IObservable LastAsync(this IObservable source, Func predicate) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return s_impl.LastAsync(source, predicate); + } + + #endregion + + #region + LastOrDefaultAsync + + + /// + /// Returns the last element of an observable sequence, or a default value if no such element exists. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// Sequence containing the last element in the observable sequence, or a default value if no such element exists. + /// is null. + public static IObservable LastOrDefaultAsync(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.LastOrDefaultAsync(source); + } + + /// + /// Returns the last element of an observable sequence that satisfies the condition in the predicate, or a default value if no such element exists. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// A predicate function to evaluate for elements in the source sequence. + /// Sequence containing the last element in the observable sequence that satisfies the condition in the predicate, or a default value if no such element exists. + /// or is null. + public static IObservable LastOrDefaultAsync(this IObservable source, Func predicate) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return s_impl.LastOrDefaultAsync(source, predicate); + } + + #endregion + + #region + LongCount + + + /// + /// Returns an observable sequence containing an that represents the total number of elements in an observable sequence. + /// + /// The type of the elements in the source sequence. + /// An observable sequence that contains elements to be counted. + /// An observable sequence containing a single element with the number of elements in the input sequence. + /// is null. + /// (Asynchronous) The number of elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable LongCount(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.LongCount(source); + } + + /// + /// Returns an observable sequence containing an that represents how many elements in the specified observable sequence satisfy a condition. + /// + /// The type of the elements in the source sequence. + /// An observable sequence that contains elements to be counted. + /// A function to test each element for a condition. + /// An observable sequence containing a single element with a number that represents how many elements in the input sequence satisfy the condition in the predicate function. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable LongCount(this IObservable source, Func predicate) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return s_impl.LongCount(source, predicate); + } + + #endregion + + #region + Max + + + /// + /// Returns the maximum element in an observable sequence. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to determine the maximum element of. + /// An observable sequence containing a single element with the maximum element in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Max(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Max(source); + } + + /// + /// Returns the maximum value in an observable sequence according to the specified comparer. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to determine the maximum element of. + /// Comparer used to compare elements. + /// An observable sequence containing a single element with the maximum element in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Max(this IObservable source, IComparer comparer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.Max(source, comparer); + } + + /// + /// Returns the maximum value in an observable sequence of values. + /// + /// A sequence of values to determine the maximum value of. + /// An observable sequence containing a single element with the maximum value in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Max(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Max(source); + } + + /// + /// Returns the maximum value in an observable sequence of values. + /// + /// A sequence of values to determine the maximum value of. + /// An observable sequence containing a single element with the maximum value in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Max(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Max(source); + } + + /// + /// Returns the maximum value in an observable sequence of values. + /// + /// A sequence of values to determine the maximum value of. + /// An observable sequence containing a single element with the maximum value in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Max(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Max(source); + } + + /// + /// Returns the maximum value in an observable sequence of values. + /// + /// A sequence of values to determine the maximum value of. + /// An observable sequence containing a single element with the maximum value in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Max(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Max(source); + } + + /// + /// Returns the maximum value in an observable sequence of values. + /// + /// A sequence of values to determine the maximum value of. + /// An observable sequence containing a single element with the maximum value in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Max(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Max(source); + } + + /// + /// Returns the maximum value in an observable sequence of nullable values. + /// + /// A sequence of nullable values to determine the maximum value of. + /// An observable sequence containing a single element with the maximum value in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Max(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Max(source); + } + + /// + /// Returns the maximum value in an observable sequence of nullable values. + /// + /// A sequence of nullable values to determine the maximum value of. + /// An observable sequence containing a single element with the maximum value in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Max(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Max(source); + } + + /// + /// Returns the maximum value in an observable sequence of nullable values. + /// + /// A sequence of nullable values to determine the maximum value of. + /// An observable sequence containing a single element with the maximum value in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Max(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Max(source); + } + + /// + /// Returns the maximum value in an observable sequence of nullable values. + /// + /// A sequence of nullable values to determine the maximum value of. + /// An observable sequence containing a single element with the maximum value in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Max(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Max(source); + } + + /// + /// Returns the maximum value in an observable sequence of nullable values. + /// + /// A sequence of nullable values to determine the maximum value of. + /// An observable sequence containing a single element with the maximum value in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Max(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Max(source); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum value. + /// + /// The type of the elements in the source sequence. + /// The type of the objects derived from the elements in the source sequence to determine the maximum of. + /// An observable sequence to determine the minimum element of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value that corresponds to the maximum element in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Max(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Max(source, selector); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum value according to the specified comparer. + /// + /// The type of the elements in the source sequence. + /// The type of the objects derived from the elements in the source sequence to determine the maximum of. + /// An observable sequence to determine the minimum element of. + /// A transform function to apply to each element. + /// Comparer used to compare elements. + /// An observable sequence containing a single element with the value that corresponds to the maximum element in the source sequence. + /// or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Max(this IObservable source, Func selector, IComparer comparer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.Max(source, selector, comparer); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the maximum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the maximum value in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Max(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Max(source, selector); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the maximum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the maximum value in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Max(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Max(source, selector); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the maximum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the maximum value in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Max(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Max(source, selector); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the maximum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the maximum value in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Max(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Max(source, selector); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the maximum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the maximum value in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Max(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Max(source, selector); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum nullable value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the maximum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the maximum value in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Max(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Max(source, selector); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum nullable value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the maximum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the maximum value in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Max(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Max(source, selector); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum nullable value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the maximum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the maximum value in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Max(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Max(source, selector); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum nullable value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the maximum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the maximum value in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Max(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Max(source, selector); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum nullable value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the maximum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the maximum value in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Max(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Max(source, selector); + } + + #endregion + + #region + MaxBy + + + /// + /// Returns the elements in an observable sequence with the maximum key value. + /// + /// The type of the elements in the source sequence. + /// The type of the key computed for each element in the source sequence. + /// An observable sequence to get the maximum elements for. + /// Key selector function. + /// An observable sequence containing a list of zero or more elements that have a maximum key value. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable> MaxBy(this IObservable source, Func keySelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + return s_impl.MaxBy(source, keySelector); + } + + /// + /// Returns the elements in an observable sequence with the maximum key value according to the specified comparer. + /// + /// The type of the elements in the source sequence. + /// The type of the key computed for each element in the source sequence. + /// An observable sequence to get the maximum elements for. + /// Key selector function. + /// Comparer used to compare key values. + /// An observable sequence containing a list of zero or more elements that have a maximum key value. + /// or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable> MaxBy(this IObservable source, Func keySelector, IComparer comparer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.MaxBy(source, keySelector, comparer); + } + + #endregion + + #region + Min + + + /// + /// Returns the minimum element in an observable sequence. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to determine the minimum element of. + /// An observable sequence containing a single element with the minimum element in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Min(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Min(source); + } + + /// + /// Returns the minimum element in an observable sequence according to the specified comparer. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to determine the minimum element of. + /// Comparer used to compare elements. + /// An observable sequence containing a single element with the minimum element in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Min(this IObservable source, IComparer comparer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.Min(source, comparer); + } + + /// + /// Returns the minimum value in an observable sequence of values. + /// + /// A sequence of values to determine the minimum value of. + /// An observable sequence containing a single element with the minimum value in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Min(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Min(source); + } + + /// + /// Returns the minimum value in an observable sequence of values. + /// + /// A sequence of values to determine the minimum value of. + /// An observable sequence containing a single element with the minimum value in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Min(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Min(source); + } + + /// + /// Returns the minimum value in an observable sequence of values. + /// + /// A sequence of values to determine the minimum value of. + /// An observable sequence containing a single element with the minimum value in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Min(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Min(source); + } + + /// + /// Returns the minimum value in an observable sequence of values. + /// + /// A sequence of values to determine the minimum value of. + /// An observable sequence containing a single element with the minimum value in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Min(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Min(source); + } + + /// + /// Returns the minimum value in an observable sequence of values. + /// + /// A sequence of values to determine the minimum value of. + /// An observable sequence containing a single element with the minimum value in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Min(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Min(source); + } + + /// + /// Returns the minimum value in an observable sequence of nullable values. + /// + /// A sequence of nullable values to determine the minimum value of. + /// An observable sequence containing a single element with the minimum value in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Min(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Min(source); + } + + /// + /// Returns the minimum value in an observable sequence of nullable values. + /// + /// A sequence of nullable values to determine the minimum value of. + /// An observable sequence containing a single element with the minimum value in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Min(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Min(source); + } + + /// + /// Returns the minimum value in an observable sequence of nullable values. + /// + /// A sequence of nullable values to determine the minimum value of. + /// An observable sequence containing a single element with the minimum value in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Min(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Min(source); + } + + /// + /// Returns the minimum value in an observable sequence of nullable values. + /// + /// A sequence of nullable values to determine the minimum value of. + /// An observable sequence containing a single element with the minimum value in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Min(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Min(source); + } + + /// + /// Returns the minimum value in an observable sequence of nullable values. + /// + /// A sequence of nullable values to determine the minimum value of. + /// An observable sequence containing a single element with the minimum value in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Min(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Min(source); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum value. + /// + /// The type of the elements in the source sequence. + /// The type of the objects derived from the elements in the source sequence to determine the minimum of. + /// An observable sequence to determine the minimum element of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value that corresponds to the minimum element in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Min(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Min(source, selector); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum value according to the specified comparer. + /// + /// The type of the elements in the source sequence. + /// The type of the objects derived from the elements in the source sequence to determine the minimum of. + /// An observable sequence to determine the minimum element of. + /// A transform function to apply to each element. + /// Comparer used to compare elements. + /// An observable sequence containing a single element with the value that corresponds to the minimum element in the source sequence. + /// or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Min(this IObservable source, Func selector, IComparer comparer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.Min(source, selector, comparer); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the minimum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the minimum value in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Min(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Min(source, selector); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the minimum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the minimum value in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Min(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Min(source, selector); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the minimum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the minimum value in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Min(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Min(source, selector); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the minimum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the minimum value in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Min(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Min(source, selector); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the minimum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the minimum value in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Min(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Min(source, selector); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum nullable value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the minimum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the minimum value in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Min(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Min(source, selector); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum nullable value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the minimum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the minimum value in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Min(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Min(source, selector); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum nullable value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the minimum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the minimum value in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Min(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Min(source, selector); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum nullable value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the minimum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the minimum value in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Min(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Min(source, selector); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum nullable value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the minimum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the minimum value in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Min(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Min(source, selector); + } + + #endregion + + #region + MinBy + + + /// + /// Returns the elements in an observable sequence with the minimum key value. + /// + /// The type of the elements in the source sequence. + /// The type of the key computed for each element in the source sequence. + /// An observable sequence to get the minimum elements for. + /// Key selector function. + /// An observable sequence containing a list of zero or more elements that have a minimum key value. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable> MinBy(this IObservable source, Func keySelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + return s_impl.MinBy(source, keySelector); + } + + /// + /// Returns the elements in an observable sequence with the minimum key value according to the specified comparer. + /// + /// The type of the elements in the source sequence. + /// The type of the key computed for each element in the source sequence. + /// An observable sequence to get the minimum elements for. + /// Key selector function. + /// Comparer used to compare key values. + /// An observable sequence containing a list of zero or more elements that have a minimum key value. + /// or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable> MinBy(this IObservable source, Func keySelector, IComparer comparer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.MinBy(source, keySelector, comparer); + } + + #endregion + + #region + SequenceEqual + + + /// + /// Determines whether two sequences are equal by comparing the elements pairwise. + /// + /// The type of the elements in the source sequence. + /// First observable sequence to compare. + /// Second observable sequence to compare. + /// An observable sequence that contains a single element which indicates whether both sequences are of equal length and their corresponding elements are equal according to the default equality comparer for their type. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable SequenceEqual(this IObservable first, IObservable second) + { + if (first == null) + { + throw new ArgumentNullException(nameof(first)); + } + + if (second == null) + { + throw new ArgumentNullException(nameof(second)); + } + + return s_impl.SequenceEqual(first, second); + } + + /// + /// Determines whether two sequences are equal by comparing the elements pairwise using a specified equality comparer. + /// + /// The type of the elements in the source sequence. + /// First observable sequence to compare. + /// Second observable sequence to compare. + /// Comparer used to compare elements of both sequences. + /// An observable sequence that contains a single element which indicates whether both sequences are of equal length and their corresponding elements are equal according to the specified equality comparer. + /// or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable SequenceEqual(this IObservable first, IObservable second, IEqualityComparer comparer) + { + if (first == null) + { + throw new ArgumentNullException(nameof(first)); + } + + if (second == null) + { + throw new ArgumentNullException(nameof(second)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.SequenceEqual(first, second, comparer); + } + + /// + /// Determines whether an observable and enumerable sequence are equal by comparing the elements pairwise. + /// + /// The type of the elements in the source sequence. + /// First observable sequence to compare. + /// Second observable sequence to compare. + /// An observable sequence that contains a single element which indicates whether both sequences are of equal length and their corresponding elements are equal according to the default equality comparer for their type. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable SequenceEqual(this IObservable first, IEnumerable second) + { + if (first == null) + { + throw new ArgumentNullException(nameof(first)); + } + + if (second == null) + { + throw new ArgumentNullException(nameof(second)); + } + + return s_impl.SequenceEqual(first, second); + } + + /// + /// Determines whether an observable and enumerable sequence are equal by comparing the elements pairwise using a specified equality comparer. + /// + /// The type of the elements in the source sequence. + /// First observable sequence to compare. + /// Second observable sequence to compare. + /// Comparer used to compare elements of both sequences. + /// An observable sequence that contains a single element which indicates whether both sequences are of equal length and their corresponding elements are equal according to the specified equality comparer. + /// or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable SequenceEqual(this IObservable first, IEnumerable second, IEqualityComparer comparer) + { + if (first == null) + { + throw new ArgumentNullException(nameof(first)); + } + + if (second == null) + { + throw new ArgumentNullException(nameof(second)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.SequenceEqual(first, second, comparer); + } + + #endregion + + #region + SingleAsync + + + /// + /// Returns the only element of an observable sequence, and reports an exception if there is not exactly one element in the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// Sequence containing the single element in the observable sequence. + /// is null. + /// (Asynchronous) The source sequence contains more than one element. -or- The source sequence is empty. + public static IObservable SingleAsync(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.SingleAsync(source); + } + + /// + /// Returns the only element of an observable sequence that satisfies the condition in the predicate, and reports an exception if there is not exactly one element in the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// A predicate function to evaluate for elements in the source sequence. + /// Sequence containing the single element in the observable sequence that satisfies the condition in the predicate. + /// or is null. + /// (Asynchronous) No element satisfies the condition in the predicate. -or- More than one element satisfies the condition in the predicate. -or- The source sequence is empty. + public static IObservable SingleAsync(this IObservable source, Func predicate) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return s_impl.SingleAsync(source, predicate); + } + + #endregion + + #region + SingleOrDefaultAsync + + + /// + /// Returns the only element of an observable sequence, or a default value if the observable sequence is empty; this method reports an exception if there is more than one element in the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// Sequence containing the single element in the observable sequence, or a default value if no such element exists. + /// is null. + /// (Asynchronous) The source sequence contains more than one element. + public static IObservable SingleOrDefaultAsync(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.SingleOrDefaultAsync(source); + } + + /// + /// Returns the only element of an observable sequence that matches the predicate, or a default value if no such element exists; this method reports an exception if there is more than one element in the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// A predicate function to evaluate for elements in the source sequence. + /// Sequence containing the single element in the observable sequence that satisfies the condition in the predicate, or a default value if no such element exists. + /// or is null. + /// (Asynchronous) The sequence contains more than one element that satisfies the condition in the predicate. + public static IObservable SingleOrDefaultAsync(this IObservable source, Func predicate) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return s_impl.SingleOrDefaultAsync(source, predicate); + } + + #endregion + + #region + Sum + + + /// + /// Computes the sum of a sequence of values. + /// + /// A sequence of values to calculate the sum of. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Sum(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Sum(source); + } + + /// + /// Computes the sum of a sequence of values. + /// + /// A sequence of values to calculate the sum of. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Sum(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Sum(source); + } + + /// + /// Computes the sum of a sequence of values. + /// + /// A sequence of values to calculate the sum of. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// is null. + /// (Asynchronous) The sum of the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Sum(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Sum(source); + } + + /// + /// Computes the sum of a sequence of values. + /// + /// A sequence of values to calculate the sum of. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// is null. + /// (Asynchronous) The sum of the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Sum(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Sum(source); + } + + /// + /// Computes the sum of a sequence of values. + /// + /// A sequence of values to calculate the sum of. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// is null. + /// (Asynchronous) The sum of the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Sum(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Sum(source); + } + + /// + /// Computes the sum of a sequence of nullable values. + /// + /// A sequence of nullable values to calculate the sum of. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Sum(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Sum(source); + } + + /// + /// Computes the sum of a sequence of nullable values. + /// + /// A sequence of nullable values to calculate the sum of. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Sum(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Sum(source); + } + + /// + /// Computes the sum of a sequence of nullable values. + /// + /// A sequence of nullable values to calculate the sum of. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// is null. + /// (Asynchronous) The sum of the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Sum(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Sum(source); + } + + /// + /// Computes the sum of a sequence of nullable values. + /// + /// A sequence of nullable values to calculate the sum of. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// is null. + /// (Asynchronous) The sum of the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Sum(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Sum(source); + } + + /// + /// Computes the sum of a sequence of nullable values. + /// + /// A sequence of nullable values to calculate the sum of. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// is null. + /// (Asynchronous) The sum of the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Sum(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Sum(source); + } + + /// + /// Computes the sum of a sequence of values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values that are used to calculate a sum. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Sum(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Sum(source, selector); + } + + /// + /// Computes the sum of a sequence of values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values that are used to calculate a sum. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Sum(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Sum(source, selector); + } + + /// + /// Computes the sum of a sequence of values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values that are used to calculate a sum. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// or is null. + /// (Asynchronous) The sum of the projected values for the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Sum(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Sum(source, selector); + } + + /// + /// Computes the sum of a sequence of values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values that are used to calculate a sum. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// or is null. + /// (Asynchronous) The sum of the projected values for the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Sum(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Sum(source, selector); + } + + /// + /// Computes the sum of a sequence of values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values that are used to calculate a sum. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// or is null. + /// (Asynchronous) The sum of the projected values for the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Sum(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Sum(source, selector); + } + + /// + /// Computes the sum of a sequence of nullable values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values that are used to calculate a sum. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Sum(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Sum(source, selector); + } + + /// + /// Computes the sum of a sequence of nullable values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values that are used to calculate a sum. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Sum(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Sum(source, selector); + } + + /// + /// Computes the sum of a sequence of nullable values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values that are used to calculate a sum. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// or is null. + /// (Asynchronous) The sum of the projected values for the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Sum(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Sum(source, selector); + } + + /// + /// Computes the sum of a sequence of nullable values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values that are used to calculate a sum. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// or is null. + /// (Asynchronous) The sum of the projected values for the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Sum(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Sum(source, selector); + } + + /// + /// Computes the sum of a sequence of nullable values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values that are used to calculate a sum. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// or is null. + /// (Asynchronous) The sum of the projected values for the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable Sum(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Sum(source, selector); + } + + #endregion + + #region + ToArray + + + /// + /// Creates an array from an observable sequence. + /// + /// The type of the elements in the source sequence. + /// The source observable sequence to get an array of elements for. + /// An observable sequence containing a single element with an array containing all the elements of the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable ToArray(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.ToArray(source); + } + + #endregion + + #region + ToDictionary + + + /// + /// Creates a dictionary from an observable sequence according to a specified key selector function. + /// + /// The type of the elements in the source sequence. + /// The type of the dictionary key computed for each element in the source sequence. + /// An observable sequence to create a dictionary for. + /// A function to extract a key from each element. + /// An observable sequence containing a single element with a dictionary mapping unique key values onto the corresponding source sequence's element. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable> ToDictionary(this IObservable source, Func keySelector) + where TKey : notnull + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + return s_impl.ToDictionary(source, keySelector); + } + + /// + /// Creates a dictionary from an observable sequence according to a specified key selector function, and a comparer. + /// + /// The type of the elements in the source sequence. + /// The type of the dictionary key computed for each element in the source sequence. + /// An observable sequence to create a dictionary for. + /// A function to extract a key from each element. + /// An equality comparer to compare keys. + /// An observable sequence containing a single element with a dictionary mapping unique key values onto the corresponding source sequence's element. + /// or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable> ToDictionary(this IObservable source, Func keySelector, IEqualityComparer comparer) + where TKey : notnull + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.ToDictionary(source, keySelector, comparer); + } + + /// + /// Creates a dictionary from an observable sequence according to a specified key selector function, and an element selector function. + /// + /// The type of the elements in the source sequence. + /// The type of the dictionary key computed for each element in the source sequence. + /// The type of the dictionary value computed for each element in the source sequence. + /// An observable sequence to create a dictionary for. + /// A function to extract a key from each element. + /// A transform function to produce a result element value from each element. + /// An observable sequence containing a single element with a dictionary mapping unique key values onto the corresponding source sequence's element. + /// or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable> ToDictionary(this IObservable source, Func keySelector, Func elementSelector) + where TKey : notnull + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (elementSelector == null) + { + throw new ArgumentNullException(nameof(elementSelector)); + } + + return s_impl.ToDictionary(source, keySelector, elementSelector); + } + + /// + /// Creates a dictionary from an observable sequence according to a specified key selector function, a comparer, and an element selector function. + /// + /// The type of the elements in the source sequence. + /// The type of the dictionary key computed for each element in the source sequence. + /// The type of the dictionary value computed for each element in the source sequence. + /// An observable sequence to create a dictionary for. + /// A function to extract a key from each element. + /// A transform function to produce a result element value from each element. + /// An equality comparer to compare keys. + /// An observable sequence containing a single element with a dictionary mapping unique key values onto the corresponding source sequence's element. + /// or or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable> ToDictionary(this IObservable source, Func keySelector, Func elementSelector, IEqualityComparer comparer) + where TKey : notnull + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (elementSelector == null) + { + throw new ArgumentNullException(nameof(elementSelector)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.ToDictionary(source, keySelector, elementSelector, comparer); + } + + #endregion + + #region + ToList + + + /// + /// Creates a list from an observable sequence. + /// + /// The type of the elements in the source sequence. + /// The source observable sequence to get a list of elements for. + /// An observable sequence containing a single element with a list containing all the elements of the source sequence. + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable> ToList(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.ToList(source); + } + + #endregion + + #region + ToLookup + + + /// + /// Creates a lookup from an observable sequence according to a specified key selector function. + /// + /// The type of the elements in the source sequence. + /// The type of the lookup key computed for each element in the source sequence. + /// An observable sequence to create a lookup for. + /// A function to extract a key from each element. + /// An observable sequence containing a single element with a lookup mapping unique key values onto the corresponding source sequence's elements. + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable> ToLookup(this IObservable source, Func keySelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + return s_impl.ToLookup(source, keySelector); + } + + /// + /// Creates a lookup from an observable sequence according to a specified key selector function, and a comparer. + /// + /// The type of the elements in the source sequence. + /// The type of the lookup key computed for each element in the source sequence. + /// An observable sequence to create a lookup for. + /// A function to extract a key from each element. + /// An equality comparer to compare keys. + /// An observable sequence containing a single element with a lookup mapping unique key values onto the corresponding source sequence's elements. + /// or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable> ToLookup(this IObservable source, Func keySelector, IEqualityComparer comparer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.ToLookup(source, keySelector, comparer); + } + + /// + /// Creates a lookup from an observable sequence according to a specified key selector function, and an element selector function. + /// + /// The type of the elements in the source sequence. + /// The type of the lookup key computed for each element in the source sequence. + /// The type of the lookup value computed for each element in the source sequence. + /// An observable sequence to create a lookup for. + /// A function to extract a key from each element. + /// A transform function to produce a result element value from each element. + /// An observable sequence containing a single element with a lookup mapping unique key values onto the corresponding source sequence's elements. + /// or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable> ToLookup(this IObservable source, Func keySelector, Func elementSelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (elementSelector == null) + { + throw new ArgumentNullException(nameof(elementSelector)); + } + + return s_impl.ToLookup(source, keySelector, elementSelector); + } + + /// + /// Creates a lookup from an observable sequence according to a specified key selector function, a comparer, and an element selector function. + /// + /// The type of the elements in the source sequence. + /// The type of the lookup key computed for each element in the source sequence. + /// The type of the lookup value computed for each element in the source sequence. + /// An observable sequence to create a lookup for. + /// A function to extract a key from each element. + /// A transform function to produce a result element value from each element. + /// An equality comparer to compare keys. + /// An observable sequence containing a single element with a lookup mapping unique key values onto the corresponding source sequence's elements. + /// or or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IObservable> ToLookup(this IObservable source, Func keySelector, Func elementSelector, IEqualityComparer comparer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (elementSelector == null) + { + throw new ArgumentNullException(nameof(elementSelector)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.ToLookup(source, keySelector, elementSelector, comparer); + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable.Async.cs b/LibExternal/System.Reactive/Linq/Observable.Async.cs new file mode 100644 index 0000000..762a5ab --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable.Async.cs @@ -0,0 +1,3604 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Reactive.Linq +{ + public static partial class Observable + { + #region FromAsyncPattern + + #region Func + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Func end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Func end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Func end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Func end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Func end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Func end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Func end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Func end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Func end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the ninth argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Func end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the ninth argument passed to the begin delegate. + /// The type of the tenth argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Func end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the ninth argument passed to the begin delegate. + /// The type of the tenth argument passed to the begin delegate. + /// The type of the eleventh argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Func end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the ninth argument passed to the begin delegate. + /// The type of the tenth argument passed to the begin delegate. + /// The type of the eleventh argument passed to the begin delegate. + /// The type of the twelfth argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Func end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the ninth argument passed to the begin delegate. + /// The type of the tenth argument passed to the begin delegate. + /// The type of the eleventh argument passed to the begin delegate. + /// The type of the twelfth argument passed to the begin delegate. + /// The type of the thirteenth argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Func end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the ninth argument passed to the begin delegate. + /// The type of the tenth argument passed to the begin delegate. + /// The type of the eleventh argument passed to the begin delegate. + /// The type of the twelfth argument passed to the begin delegate. + /// The type of the thirteenth argument passed to the begin delegate. + /// The type of the fourteenth argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Func end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + #endregion + + #region Action + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Action end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Action end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Action end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Action end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Action end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Action end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Action end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Action end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Action end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the ninth argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Action end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the ninth argument passed to the begin delegate. + /// The type of the tenth argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Action end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the ninth argument passed to the begin delegate. + /// The type of the tenth argument passed to the begin delegate. + /// The type of the eleventh argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Action end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the ninth argument passed to the begin delegate. + /// The type of the tenth argument passed to the begin delegate. + /// The type of the eleventh argument passed to the begin delegate. + /// The type of the twelfth argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Action end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the ninth argument passed to the begin delegate. + /// The type of the tenth argument passed to the begin delegate. + /// The type of the eleventh argument passed to the begin delegate. + /// The type of the twelfth argument passed to the begin delegate. + /// The type of the thirteenth argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Action end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the ninth argument passed to the begin delegate. + /// The type of the tenth argument passed to the begin delegate. + /// The type of the eleventh argument passed to the begin delegate. + /// The type of the twelfth argument passed to the begin delegate. + /// The type of the thirteenth argument passed to the begin delegate. + /// The type of the fourteenth argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. + [Obsolete(Constants_Linq.UseTaskFromAsyncPattern)] + public static Func> FromAsyncPattern(Func begin, Action end) + { + if (begin == null) + { + throw new ArgumentNullException(nameof(begin)); + } + + if (end == null) + { + throw new ArgumentNullException(nameof(end)); + } + + return s_impl.FromAsyncPattern(begin, end); + } + + #endregion + + #endregion + + #region Start[Async] + + #region Func + + /// + /// Invokes the specified function asynchronously, surfacing the result through an observable sequence. + /// + /// The type of the result returned by the function. + /// Function to run asynchronously. + /// An observable sequence exposing the function's result value, or an exception. + /// is null. + /// + /// + /// The function is called immediately, not during the subscription of the resulting sequence. + /// Multiple subscriptions to the resulting sequence can observe the function's result. + /// + /// + public static IObservable Start(Func function) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + return s_impl.Start(function); + } + + /// + /// Invokes the specified function asynchronously on the specified scheduler, surfacing the result through an observable sequence + /// + /// The type of the result returned by the function. + /// Function to run asynchronously. + /// Scheduler to run the function on. + /// An observable sequence exposing the function's result value, or an exception. + /// or is null. + /// + /// + /// The function is called immediately, not during the subscription of the resulting sequence. + /// Multiple subscriptions to the resulting sequence can observe the function's result. + /// + /// + public static IObservable Start(Func function, IScheduler scheduler) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Start(function, scheduler); + } + + /// + /// Invokes the asynchronous function, surfacing the result through an observable sequence. + /// + /// The type of the result returned by the asynchronous function. + /// Asynchronous function to run. + /// An observable sequence exposing the function's result value, or an exception. + /// is null. + /// + /// + /// The function is started immediately, not during the subscription of the resulting sequence. + /// Multiple subscriptions to the resulting sequence can observe the function's result. + /// + /// + public static IObservable StartAsync(Func> functionAsync) + { + if (functionAsync == null) + { + throw new ArgumentNullException(nameof(functionAsync)); + } + + return s_impl.StartAsync(functionAsync); + } + + /// + /// Invokes the asynchronous function, surfacing the result through an observable sequence. + /// + /// The type of the result returned by the asynchronous function. + /// Asynchronous function to run. + /// Scheduler on which to notify observers. + /// An observable sequence exposing the function's result value, or an exception. + /// is null or is null. + /// + /// + /// The function is started immediately, not during the subscription of the resulting sequence. + /// Multiple subscriptions to the resulting sequence can observe the function's result. + /// + /// + public static IObservable StartAsync(Func> functionAsync, IScheduler scheduler) + { + if (functionAsync == null) + { + throw new ArgumentNullException(nameof(functionAsync)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.StartAsync(functionAsync, new TaskObservationOptions.Value(scheduler, ignoreExceptionsAfterUnsubscribe: false)); + } + + /// + /// Invokes the asynchronous function, surfacing the result through an observable sequence. + /// + /// The type of the result returned by the asynchronous function. + /// Asynchronous function to run. + /// Controls how the tasks's progress is observed. + /// An observable sequence exposing the function's result value, or an exception. + /// is null. + /// + /// + /// The function is started immediately, not during the subscription of the resulting sequence. + /// Multiple subscriptions to the resulting sequence can observe the function's result. + /// + /// + public static IObservable StartAsync(Func> functionAsync, TaskObservationOptions options) + { + if (functionAsync == null) + { + throw new ArgumentNullException(nameof(functionAsync)); + } + + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + return s_impl.StartAsync(functionAsync, options.ToValue()); + } + + /// + /// Invokes the asynchronous function, surfacing the result through an observable sequence. + /// The CancellationToken is shared by all subscriptions on the resulting observable sequence. See the remarks section for more information. + /// + /// The type of the result returned by the asynchronous function. + /// Asynchronous function to run. + /// An observable sequence exposing the function's result value, or an exception. + /// is null. + /// + /// + /// The function is started immediately, not during the subscription of the resulting sequence. + /// Multiple subscriptions to the resulting sequence can observe the function's result. + /// + /// If any subscription to the resulting sequence is disposed, the CancellationToken is set. The observer associated to the disposed + /// subscription won't see the TaskCanceledException, but other observers will. You can protect against this using the Catch operator. + /// Be careful when handing out the resulting sequence because of this behavior. The most common use is to have a single subscription + /// to the resulting sequence, which controls the CancellationToken state. Alternatively, you can control subscription behavior using + /// multicast operators. + /// + /// + /// + public static IObservable StartAsync(Func> functionAsync) + { + if (functionAsync == null) + { + throw new ArgumentNullException(nameof(functionAsync)); + } + + return s_impl.StartAsync(functionAsync); + } + + /// + /// Invokes the asynchronous function, surfacing the result through an observable sequence. + /// The CancellationToken is shared by all subscriptions on the resulting observable sequence. See the remarks section for more information. + /// + /// The type of the result returned by the asynchronous function. + /// Asynchronous function to run. + /// Scheduler on which to notify observers. + /// An observable sequence exposing the function's result value, or an exception. + /// is null or is null. + /// + /// + /// The function is started immediately, not during the subscription of the resulting sequence. + /// Multiple subscriptions to the resulting sequence can observe the function's result. + /// + /// If any subscription to the resulting sequence is disposed, the CancellationToken is set. The observer associated to the disposed + /// subscription won't see the TaskCanceledException, but other observers will. You can protect against this using the Catch operator. + /// Be careful when handing out the resulting sequence because of this behavior. The most common use is to have a single subscription + /// to the resulting sequence, which controls the CancellationToken state. Alternatively, you can control subscription behavior using + /// multicast operators. + /// + /// + /// + public static IObservable StartAsync(Func> functionAsync, IScheduler scheduler) + { + if (functionAsync == null) + { + throw new ArgumentNullException(nameof(functionAsync)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.StartAsync(functionAsync, new TaskObservationOptions.Value(scheduler, ignoreExceptionsAfterUnsubscribe: false)); + } + + /// + /// Invokes the asynchronous function, surfacing the result through an observable sequence. + /// The CancellationToken is shared by all subscriptions on the resulting observable sequence. See the remarks section for more information. + /// + /// The type of the result returned by the asynchronous function. + /// Asynchronous function to run. + /// Controls how the tasks's progress is observed. + /// An observable sequence exposing the function's result value, or an exception. + /// is null. + /// + /// + /// The function is started immediately, not during the subscription of the resulting sequence. + /// Multiple subscriptions to the resulting sequence can observe the function's result. + /// + /// If any subscription to the resulting sequence is disposed, the CancellationToken is set. The observer associated to the disposed + /// subscription won't see the TaskCanceledException, but other observers will. You can protect against this using the Catch operator. + /// Be careful when handing out the resulting sequence because of this behavior. The most common use is to have a single subscription + /// to the resulting sequence, which controls the CancellationToken state. Alternatively, you can control subscription behavior using + /// multicast operators. + /// + /// + /// + public static IObservable StartAsync(Func> functionAsync, TaskObservationOptions options) + { + if (functionAsync == null) + { + throw new ArgumentNullException(nameof(functionAsync)); + } + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + return s_impl.StartAsync(functionAsync, options.ToValue()); + } + + #endregion + + #region Action + + /// + /// Invokes the action asynchronously, surfacing the result through an observable sequence. + /// + /// Action to run asynchronously. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// is null. + /// + /// + /// The action is called immediately, not during the subscription of the resulting sequence. + /// Multiple subscriptions to the resulting sequence can observe the action's outcome. + /// + /// + public static IObservable Start(Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return s_impl.Start(action); + } + + /// + /// Invokes the action asynchronously on the specified scheduler, surfacing the result through an observable sequence. + /// + /// Action to run asynchronously. + /// Scheduler to run the action on. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// or is null. + /// + /// + /// The action is called immediately, not during the subscription of the resulting sequence. + /// Multiple subscriptions to the resulting sequence can observe the action's outcome. + /// + /// + public static IObservable Start(Action action, IScheduler scheduler) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Start(action, scheduler); + } + + /// + /// Invokes the asynchronous action, surfacing the result through an observable sequence. + /// + /// Asynchronous action to run. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// is null. + /// + /// + /// The action is started immediately, not during the subscription of the resulting sequence. + /// Multiple subscriptions to the resulting sequence can observe the action's outcome. + /// + /// + public static IObservable StartAsync(Func actionAsync) + { + if (actionAsync == null) + { + throw new ArgumentNullException(nameof(actionAsync)); + } + + return s_impl.StartAsync(actionAsync); + } + + /// + /// Invokes the asynchronous action, surfacing the result through an observable sequence. + /// + /// Asynchronous action to run. + /// Scheduler on which to notify observers. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// is null or is null. + /// + /// + /// The action is started immediately, not during the subscription of the resulting sequence. + /// Multiple subscriptions to the resulting sequence can observe the action's outcome. + /// + /// + public static IObservable StartAsync(Func actionAsync, IScheduler scheduler) + { + if (actionAsync == null) + { + throw new ArgumentNullException(nameof(actionAsync)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.StartAsync(actionAsync, new TaskObservationOptions.Value(scheduler, ignoreExceptionsAfterUnsubscribe: false)); + } + + /// + /// Invokes the asynchronous action, surfacing the result through an observable sequence. + /// + /// Asynchronous action to run. + /// Controls how the tasks's progress is observed. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// is null. + /// + /// + /// The action is started immediately, not during the subscription of the resulting sequence. + /// Multiple subscriptions to the resulting sequence can observe the action's outcome. + /// + /// + public static IObservable StartAsync(Func actionAsync, TaskObservationOptions options) + { + if (actionAsync == null) + { + throw new ArgumentNullException(nameof(actionAsync)); + } + + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + return s_impl.StartAsync(actionAsync, options.ToValue()); + } + + /// + /// Invokes the asynchronous action, surfacing the result through an observable sequence. + /// The CancellationToken is shared by all subscriptions on the resulting observable sequence. See the remarks section for more information. + /// + /// Asynchronous action to run. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// is null. + /// + /// + /// The action is started immediately, not during the subscription of the resulting sequence. + /// Multiple subscriptions to the resulting sequence can observe the action's outcome. + /// + /// If any subscription to the resulting sequence is disposed, the CancellationToken is set. The observer associated to the disposed + /// subscription won't see the TaskCanceledException, but other observers will. You can protect against this using the Catch operator. + /// Be careful when handing out the resulting sequence because of this behavior. The most common use is to have a single subscription + /// to the resulting sequence, which controls the CancellationToken state. Alternatively, you can control subscription behavior using + /// multicast operators. + /// + /// + /// + public static IObservable StartAsync(Func actionAsync) + { + if (actionAsync == null) + { + throw new ArgumentNullException(nameof(actionAsync)); + } + + return s_impl.StartAsync(actionAsync); + } + + /// + /// Invokes the asynchronous action, surfacing the result through an observable sequence. + /// The CancellationToken is shared by all subscriptions on the resulting observable sequence. See the remarks section for more information. + /// + /// Asynchronous action to run. + /// Scheduler on which to notify observers. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// is null or is null. + /// + /// + /// The action is started immediately, not during the subscription of the resulting sequence. + /// Multiple subscriptions to the resulting sequence can observe the action's outcome. + /// + /// If any subscription to the resulting sequence is disposed, the CancellationToken is set. The observer associated to the disposed + /// subscription won't see the TaskCanceledException, but other observers will. You can protect against this using the Catch operator. + /// Be careful when handing out the resulting sequence because of this behavior. The most common use is to have a single subscription + /// to the resulting sequence, which controls the CancellationToken state. Alternatively, you can control subscription behavior using + /// multicast operators. + /// + /// + /// + public static IObservable StartAsync(Func actionAsync, IScheduler scheduler) + { + if (actionAsync == null) + { + throw new ArgumentNullException(nameof(actionAsync)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.StartAsync(actionAsync, new TaskObservationOptions.Value(scheduler, ignoreExceptionsAfterUnsubscribe: false)); + } + + /// + /// Invokes the asynchronous action, surfacing the result through an observable sequence. + /// The CancellationToken is shared by all subscriptions on the resulting observable sequence. See the remarks section for more information. + /// + /// Asynchronous action to run. + /// Controls how the tasks's progress is observed. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// is null. + /// + /// + /// The action is started immediately, not during the subscription of the resulting sequence. + /// Multiple subscriptions to the resulting sequence can observe the action's outcome. + /// + /// If any subscription to the resulting sequence is disposed, the CancellationToken is set. The observer associated to the disposed + /// subscription won't see the TaskCanceledException, but other observers will. You can protect against this using the Catch operator. + /// Be careful when handing out the resulting sequence because of this behavior. The most common use is to have a single subscription + /// to the resulting sequence, which controls the CancellationToken state. Alternatively, you can control subscription behavior using + /// multicast operators. + /// + /// + /// + public static IObservable StartAsync(Func actionAsync, TaskObservationOptions options) + { + if (actionAsync == null) + { + throw new ArgumentNullException(nameof(actionAsync)); + } + + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + return s_impl.StartAsync(actionAsync, options.ToValue()); + } + + #endregion + + #endregion + + #region FromAsync + + + #region Func + + /// + /// Converts an asynchronous function into an observable sequence. Each subscription to the resulting sequence causes the function to be started. + /// + /// The type of the result returned by the asynchronous function. + /// Asynchronous function to convert. + /// An observable sequence exposing the result of invoking the function, or an exception. + /// is null. + public static IObservable FromAsync(Func> functionAsync) + { + if (functionAsync == null) + { + throw new ArgumentNullException(nameof(functionAsync)); + } + + return s_impl.FromAsync(functionAsync); + } + + /// + /// Converts an asynchronous function into an observable sequence. Each subscription to the resulting sequence causes the function to be started. + /// + /// The type of the result returned by the asynchronous function. + /// Asynchronous function to convert. + /// Scheduler on which to notify observers. + /// An observable sequence exposing the result of invoking the function, or an exception. + /// is null or is null. + public static IObservable FromAsync(Func> functionAsync, IScheduler scheduler) + { + if (functionAsync == null) + { + throw new ArgumentNullException(nameof(functionAsync)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.FromAsync(functionAsync, new TaskObservationOptions.Value(scheduler, ignoreExceptionsAfterUnsubscribe: false)); + } + + /// + /// Converts an asynchronous function into an observable sequence. Each subscription to the resulting sequence causes the function to be started. + /// + /// The type of the result returned by the asynchronous function. + /// Asynchronous function to convert. + /// Controls how the tasks's progress is observed. + /// An observable sequence exposing the result of invoking the function, or an exception. + /// is null. + public static IObservable FromAsync(Func> functionAsync, TaskObservationOptions options) + { + if (functionAsync == null) + { + throw new ArgumentNullException(nameof(functionAsync)); + } + + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + return s_impl.FromAsync(functionAsync, options.ToValue()); + } + + /// + /// Converts an asynchronous function into an observable sequence. Each subscription to the resulting sequence causes the function to be started. + /// The CancellationToken passed to the asynchronous function is tied to the observable sequence's subscription that triggered the function's invocation and can be used for best-effort cancellation. + /// + /// The type of the result returned by the asynchronous function. + /// Asynchronous function to convert. + /// An observable sequence exposing the result of invoking the function, or an exception. + /// is null. + /// When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous function will be signaled. + public static IObservable FromAsync(Func> functionAsync) + { + if (functionAsync == null) + { + throw new ArgumentNullException(nameof(functionAsync)); + } + + return s_impl.FromAsync(functionAsync); + } + + /// + /// Converts an asynchronous function into an observable sequence. Each subscription to the resulting sequence causes the function to be started. + /// The CancellationToken passed to the asynchronous function is tied to the observable sequence's subscription that triggered the function's invocation and can be used for best-effort cancellation. + /// + /// The type of the result returned by the asynchronous function. + /// Asynchronous function to convert. + /// Scheduler on which to notify observers. + /// An observable sequence exposing the result of invoking the function, or an exception. + /// is null or is null. + /// When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous function will be signaled. + public static IObservable FromAsync(Func> functionAsync, IScheduler scheduler) + { + if (functionAsync == null) + { + throw new ArgumentNullException(nameof(functionAsync)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.FromAsync(functionAsync, new TaskObservationOptions.Value(scheduler, ignoreExceptionsAfterUnsubscribe: false)); + } + + /// + /// Converts an asynchronous function into an observable sequence. Each subscription to the resulting sequence causes the function to be started. + /// The CancellationToken passed to the asynchronous function is tied to the observable sequence's subscription that triggered the function's invocation and can be used for best-effort cancellation. + /// + /// The type of the result returned by the asynchronous function. + /// Asynchronous function to convert. + /// Controls how the tasks's progress is observed. + /// An observable sequence exposing the result of invoking the function, or an exception. + /// is null. + /// When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous function will be signaled. + public static IObservable FromAsync(Func> functionAsync, TaskObservationOptions options) + { + if (functionAsync == null) + { + throw new ArgumentNullException(nameof(functionAsync)); + } + + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + return s_impl.FromAsync(functionAsync, options.ToValue()); + } + + #endregion + + #region Action + + /// + /// Converts an asynchronous action into an observable sequence. Each subscription to the resulting sequence causes the action to be started. + /// + /// Asynchronous action to convert. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// is null. + public static IObservable FromAsync(Func actionAsync) + { + if (actionAsync == null) + { + throw new ArgumentNullException(nameof(actionAsync)); + } + + return s_impl.FromAsync(actionAsync); + } + + /// + /// Converts an asynchronous action into an observable sequence. Each subscription to the resulting sequence causes the action to be started. + /// + /// Asynchronous action to convert. + /// Scheduler on which to notify observers. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// is null or is null. + public static IObservable FromAsync(Func actionAsync, IScheduler scheduler) + { + if (actionAsync == null) + { + throw new ArgumentNullException(nameof(actionAsync)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.FromAsync(actionAsync, new TaskObservationOptions.Value(scheduler, ignoreExceptionsAfterUnsubscribe: false)); + } + + /// + /// Converts an asynchronous action into an observable sequence. Each subscription to the resulting sequence causes the action to be started. + /// + /// Asynchronous action to convert. + /// Controls how the tasks's progress is observed. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// is null. + public static IObservable FromAsync(Func actionAsync, TaskObservationOptions options) + { + if (actionAsync == null) + { + throw new ArgumentNullException(nameof(actionAsync)); + } + + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + return s_impl.FromAsync(actionAsync, options.ToValue()); + } + + /// + /// Converts an asynchronous action into an observable sequence. Each subscription to the resulting sequence causes the action to be started. + /// The CancellationToken passed to the asynchronous action is tied to the observable sequence's subscription that triggered the action's invocation and can be used for best-effort cancellation. + /// + /// Asynchronous action to convert. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous function will be signaled. + /// is null. + public static IObservable FromAsync(Func actionAsync) + { + if (actionAsync == null) + { + throw new ArgumentNullException(nameof(actionAsync)); + } + + return s_impl.FromAsync(actionAsync); + } + + /// + /// Converts an asynchronous action into an observable sequence. Each subscription to the resulting sequence causes the action to be started. + /// The CancellationToken passed to the asynchronous action is tied to the observable sequence's subscription that triggered the action's invocation and can be used for best-effort cancellation. + /// + /// Asynchronous action to convert. + /// Scheduler on which to notify observers. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous function will be signaled. + /// is null or is null. + public static IObservable FromAsync(Func actionAsync, IScheduler scheduler) + { + if (actionAsync == null) + { + throw new ArgumentNullException(nameof(actionAsync)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.FromAsync(actionAsync, new TaskObservationOptions.Value(scheduler, ignoreExceptionsAfterUnsubscribe: false)); + } + + /// + /// Converts an asynchronous action into an observable sequence. Each subscription to the resulting sequence causes the action to be started. + /// The CancellationToken passed to the asynchronous action is tied to the observable sequence's subscription that triggered the action's invocation and can be used for best-effort cancellation. + /// + /// Asynchronous action to convert. + /// Controls how the tasks's progress is observed. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous function will be signaled. + /// is null. + public static IObservable FromAsync(Func actionAsync, TaskObservationOptions options) + { + if (actionAsync == null) + { + throw new ArgumentNullException(nameof(actionAsync)); + } + + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + return s_impl.FromAsync(actionAsync, options.ToValue()); + } + + #endregion + + + + #endregion + + #region ToAsync + + #region Func + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// is null. + public static Func> ToAsync(this Func function) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + return s_impl.ToAsync(function); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// or is null. + public static Func> ToAsync(this Func function, IScheduler scheduler) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(function, scheduler); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// The type of the first argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// is null. + public static Func> ToAsync(this Func function) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + return s_impl.ToAsync(function); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// The type of the first argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// or is null. + public static Func> ToAsync(this Func function, IScheduler scheduler) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(function, scheduler); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// is null. + public static Func> ToAsync(this Func function) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + return s_impl.ToAsync(function); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// or is null. + public static Func> ToAsync(this Func function, IScheduler scheduler) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(function, scheduler); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// is null. + public static Func> ToAsync(this Func function) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + return s_impl.ToAsync(function); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// or is null. + public static Func> ToAsync(this Func function, IScheduler scheduler) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(function, scheduler); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// is null. + public static Func> ToAsync(this Func function) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + return s_impl.ToAsync(function); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// or is null. + public static Func> ToAsync(this Func function, IScheduler scheduler) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(function, scheduler); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// is null. + public static Func> ToAsync(this Func function) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + return s_impl.ToAsync(function); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// or is null. + public static Func> ToAsync(this Func function, IScheduler scheduler) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(function, scheduler); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// is null. + public static Func> ToAsync(this Func function) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + return s_impl.ToAsync(function); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// or is null. + public static Func> ToAsync(this Func function, IScheduler scheduler) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(function, scheduler); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// is null. + public static Func> ToAsync(this Func function) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + return s_impl.ToAsync(function); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// or is null. + public static Func> ToAsync(this Func function, IScheduler scheduler) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(function, scheduler); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// is null. + public static Func> ToAsync(this Func function) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + return s_impl.ToAsync(function); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// or is null. + public static Func> ToAsync(this Func function, IScheduler scheduler) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(function, scheduler); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// is null. + public static Func> ToAsync(this Func function) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + return s_impl.ToAsync(function); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// or is null. + public static Func> ToAsync(this Func function, IScheduler scheduler) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(function, scheduler); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// is null. + public static Func> ToAsync(this Func function) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + return s_impl.ToAsync(function); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// or is null. + public static Func> ToAsync(this Func function, IScheduler scheduler) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(function, scheduler); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the eleventh argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// is null. + public static Func> ToAsync(this Func function) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + return s_impl.ToAsync(function); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the eleventh argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// or is null. + public static Func> ToAsync(this Func function, IScheduler scheduler) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(function, scheduler); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the eleventh argument passed to the function. + /// The type of the twelfth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// is null. + public static Func> ToAsync(this Func function) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + return s_impl.ToAsync(function); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the eleventh argument passed to the function. + /// The type of the twelfth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// or is null. + public static Func> ToAsync(this Func function, IScheduler scheduler) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(function, scheduler); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the eleventh argument passed to the function. + /// The type of the twelfth argument passed to the function. + /// The type of the thirteenth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// is null. + public static Func> ToAsync(this Func function) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + return s_impl.ToAsync(function); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the eleventh argument passed to the function. + /// The type of the twelfth argument passed to the function. + /// The type of the thirteenth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// or is null. + public static Func> ToAsync(this Func function, IScheduler scheduler) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(function, scheduler); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the eleventh argument passed to the function. + /// The type of the twelfth argument passed to the function. + /// The type of the thirteenth argument passed to the function. + /// The type of the fourteenth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// is null. + public static Func> ToAsync(this Func function) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + return s_impl.ToAsync(function); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the eleventh argument passed to the function. + /// The type of the twelfth argument passed to the function. + /// The type of the thirteenth argument passed to the function. + /// The type of the fourteenth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// or is null. + public static Func> ToAsync(this Func function, IScheduler scheduler) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(function, scheduler); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the eleventh argument passed to the function. + /// The type of the twelfth argument passed to the function. + /// The type of the thirteenth argument passed to the function. + /// The type of the fourteenth argument passed to the function. + /// The type of the fifteenth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// is null. + public static Func> ToAsync(this Func function) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + return s_impl.ToAsync(function); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the eleventh argument passed to the function. + /// The type of the twelfth argument passed to the function. + /// The type of the thirteenth argument passed to the function. + /// The type of the fourteenth argument passed to the function. + /// The type of the fifteenth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// or is null. + public static Func> ToAsync(this Func function, IScheduler scheduler) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(function, scheduler); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the eleventh argument passed to the function. + /// The type of the twelfth argument passed to the function. + /// The type of the thirteenth argument passed to the function. + /// The type of the fourteenth argument passed to the function. + /// The type of the fifteenth argument passed to the function. + /// The type of the sixteenth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// is null. + public static Func> ToAsync(this Func function) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + return s_impl.ToAsync(function); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the eleventh argument passed to the function. + /// The type of the twelfth argument passed to the function. + /// The type of the thirteenth argument passed to the function. + /// The type of the fourteenth argument passed to the function. + /// The type of the fifteenth argument passed to the function. + /// The type of the sixteenth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// or is null. + public static Func> ToAsync(this Func function, IScheduler scheduler) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(function, scheduler); + } + + #endregion + + #region Action + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// is null. + public static Func> ToAsync(this Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return s_impl.ToAsync(action); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// or is null. + public static Func> ToAsync(this Action action, IScheduler scheduler) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(action, scheduler); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// The type of the first argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// is null. + public static Func> ToAsync(this Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return s_impl.ToAsync(action); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// The type of the first argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// or is null. + public static Func> ToAsync(this Action action, IScheduler scheduler) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(action, scheduler); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// is null. + public static Func> ToAsync(this Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return s_impl.ToAsync(action); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// or is null. + public static Func> ToAsync(this Action action, IScheduler scheduler) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(action, scheduler); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// is null. + public static Func> ToAsync(this Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return s_impl.ToAsync(action); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// or is null. + public static Func> ToAsync(this Action action, IScheduler scheduler) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(action, scheduler); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// is null. + public static Func> ToAsync(this Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return s_impl.ToAsync(action); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// or is null. + public static Func> ToAsync(this Action action, IScheduler scheduler) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(action, scheduler); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// is null. + public static Func> ToAsync(this Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return s_impl.ToAsync(action); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// or is null. + public static Func> ToAsync(this Action action, IScheduler scheduler) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(action, scheduler); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// is null. + public static Func> ToAsync(this Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return s_impl.ToAsync(action); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// or is null. + public static Func> ToAsync(this Action action, IScheduler scheduler) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(action, scheduler); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// is null. + public static Func> ToAsync(this Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return s_impl.ToAsync(action); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// or is null. + public static Func> ToAsync(this Action action, IScheduler scheduler) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(action, scheduler); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// is null. + public static Func> ToAsync(this Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return s_impl.ToAsync(action); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// or is null. + public static Func> ToAsync(this Action action, IScheduler scheduler) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(action, scheduler); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// is null. + public static Func> ToAsync(this Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return s_impl.ToAsync(action); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// or is null. + public static Func> ToAsync(this Action action, IScheduler scheduler) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(action, scheduler); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// is null. + public static Func> ToAsync(this Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return s_impl.ToAsync(action); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// or is null. + public static Func> ToAsync(this Action action, IScheduler scheduler) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(action, scheduler); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// The type of the eleventh argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// is null. + public static Func> ToAsync(this Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return s_impl.ToAsync(action); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// The type of the eleventh argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// or is null. + public static Func> ToAsync(this Action action, IScheduler scheduler) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(action, scheduler); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// The type of the eleventh argument passed to the action. + /// The type of the twelfth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// is null. + public static Func> ToAsync(this Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return s_impl.ToAsync(action); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// The type of the eleventh argument passed to the action. + /// The type of the twelfth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// or is null. + public static Func> ToAsync(this Action action, IScheduler scheduler) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(action, scheduler); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// The type of the eleventh argument passed to the action. + /// The type of the twelfth argument passed to the action. + /// The type of the thirteenth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// is null. + public static Func> ToAsync(this Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return s_impl.ToAsync(action); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// The type of the eleventh argument passed to the action. + /// The type of the twelfth argument passed to the action. + /// The type of the thirteenth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// or is null. + public static Func> ToAsync(this Action action, IScheduler scheduler) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(action, scheduler); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// The type of the eleventh argument passed to the action. + /// The type of the twelfth argument passed to the action. + /// The type of the thirteenth argument passed to the action. + /// The type of the fourteenth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// is null. + public static Func> ToAsync(this Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return s_impl.ToAsync(action); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// The type of the eleventh argument passed to the action. + /// The type of the twelfth argument passed to the action. + /// The type of the thirteenth argument passed to the action. + /// The type of the fourteenth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// or is null. + public static Func> ToAsync(this Action action, IScheduler scheduler) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(action, scheduler); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// The type of the eleventh argument passed to the action. + /// The type of the twelfth argument passed to the action. + /// The type of the thirteenth argument passed to the action. + /// The type of the fourteenth argument passed to the action. + /// The type of the fifteenth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// is null. + public static Func> ToAsync(this Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return s_impl.ToAsync(action); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// The type of the eleventh argument passed to the action. + /// The type of the twelfth argument passed to the action. + /// The type of the thirteenth argument passed to the action. + /// The type of the fourteenth argument passed to the action. + /// The type of the fifteenth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// or is null. + public static Func> ToAsync(this Action action, IScheduler scheduler) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(action, scheduler); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// The type of the eleventh argument passed to the action. + /// The type of the twelfth argument passed to the action. + /// The type of the thirteenth argument passed to the action. + /// The type of the fourteenth argument passed to the action. + /// The type of the fifteenth argument passed to the action. + /// The type of the sixteenth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// is null. + public static Func> ToAsync(this Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return s_impl.ToAsync(action); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// The type of the eleventh argument passed to the action. + /// The type of the twelfth argument passed to the action. + /// The type of the thirteenth argument passed to the action. + /// The type of the fourteenth argument passed to the action. + /// The type of the fifteenth argument passed to the action. + /// The type of the sixteenth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// or is null. + public static Func> ToAsync(this Action action, IScheduler scheduler) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToAsync(action, scheduler); + } + + #endregion + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable.Awaiter.cs b/LibExternal/System.Reactive/Linq/Observable.Awaiter.cs new file mode 100644 index 0000000..e2e3d70 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable.Awaiter.cs @@ -0,0 +1,86 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Subjects; +using System.Threading; + +namespace System.Reactive.Linq +{ + public static partial class Observable + { + /// + /// Gets an awaiter that returns the last value of the observable sequence or throws an exception if the sequence is empty. + /// This operation subscribes to the observable sequence, making it hot. + /// + /// The type of the elements in the source sequence. + /// Source sequence to await. + /// Object that can be awaited. + /// is null. + public static AsyncSubject GetAwaiter(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.GetAwaiter(source); + } + + /// + /// Gets an awaiter that returns the last value of the observable sequence or throws an exception if the sequence is empty. + /// This operation subscribes and connects to the observable sequence, making it hot. + /// + /// The type of the elements in the source sequence. + /// Source sequence to await. + /// Object that can be awaited. + /// is null. + public static AsyncSubject GetAwaiter(this IConnectableObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.GetAwaiter(source); + } + + /// + /// Gets an awaiter that returns the last value of the observable sequence or throws an exception if the sequence is empty. + /// This operation subscribes to the observable sequence, making it hot. The supplied CancellationToken can be used to cancel the subscription. + /// + /// The type of the elements in the source sequence. + /// Source sequence to await. + /// Cancellation token. + /// Object that can be awaited. + /// is null. + public static AsyncSubject RunAsync(this IObservable source, CancellationToken cancellationToken) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.RunAsync(source, cancellationToken); + } + + /// + /// Gets an awaiter that returns the last value of the observable sequence or throws an exception if the sequence is empty. + /// This operation subscribes and connects to the observable sequence, making it hot. The supplied CancellationToken can be used to cancel the subscription and connection. + /// + /// The type of the elements in the source sequence. + /// Source sequence to await. + /// Cancellation token. + /// Object that can be awaited. + /// is null. + public static AsyncSubject RunAsync(this IConnectableObservable source, CancellationToken cancellationToken) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.RunAsync(source, cancellationToken); + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable.Binding.cs b/LibExternal/System.Reactive/Linq/Observable.Binding.cs new file mode 100644 index 0000000..8248f31 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable.Binding.cs @@ -0,0 +1,940 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reactive.Subjects; + +namespace System.Reactive.Linq +{ + public static partial class Observable + { + #region + Multicast + + + /// + /// Multicasts the source sequence notifications through the specified subject to the resulting connectable observable. Upon connection of the + /// connectable observable, the subject is subscribed to the source exactly one, and messages are forwarded to the observers registered with + /// the connectable observable. For specializations with fixed subject types, see Publish, PublishLast, and Replay. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence whose elements will be pushed into the specified subject. + /// Subject to push source elements into. + /// A connectable observable sequence that upon connection causes the source sequence to push results into the specified subject. + /// or is null. + public static IConnectableObservable Multicast(this IObservable source, ISubject subject) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (subject == null) + { + throw new ArgumentNullException(nameof(subject)); + } + + return s_impl.Multicast(source, subject); + } + + /// + /// Multicasts the source sequence notifications through an instantiated subject into all uses of the sequence within a selector function. Each + /// subscription to the resulting sequence causes a separate multicast invocation, exposing the sequence resulting from the selector function's + /// invocation. For specializations with fixed subject types, see Publish, PublishLast, and Replay. + /// + /// The type of the elements in the source sequence. + /// The type of the elements produced by the intermediate subject. + /// The type of the elements in the result sequence. + /// Source sequence which will be multicasted in the specified selector function. + /// Factory function to create an intermediate subject through which the source sequence's elements will be multicast to the selector function. + /// Selector function which can use the multicasted source sequence subject to the policies enforced by the created subject. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// or or is null. + public static IObservable Multicast(this IObservable source, Func> subjectSelector, Func, IObservable> selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (subjectSelector == null) + { + throw new ArgumentNullException(nameof(subjectSelector)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Multicast(source, subjectSelector, selector); + } + + #endregion + + #region + Publish + + + /// + /// Returns a connectable observable sequence that shares a single subscription to the underlying sequence. + /// This operator is a specialization of Multicast using a regular . + /// + /// The type of the elements in the source sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// A connectable observable sequence that shares a single subscription to the underlying sequence. + /// is null. + /// Subscribers will receive all notifications of the source from the time of the subscription on. + /// + public static IConnectableObservable Publish(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Publish(source); + } + + /// + /// Returns an observable sequence that is the result of invoking the selector on a connectable observable sequence that shares a single subscription to the underlying sequence. + /// This operator is a specialization of Multicast using a regular . + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive all notifications of the source from the time of the subscription on. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// or is null. + /// + public static IObservable Publish(this IObservable source, Func, IObservable> selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Publish(source, selector); + } + + /// + /// Returns a connectable observable sequence that shares a single subscription to the underlying sequence and starts with initialValue. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Initial value received by observers upon subscription. + /// A connectable observable sequence that shares a single subscription to the underlying sequence. + /// is null. + /// Subscribers will receive immediately receive the initial value, followed by all notifications of the source from the time of the subscription on. + /// + public static IConnectableObservable Publish(this IObservable source, TSource initialValue) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Publish(source, initialValue); + } + + /// + /// Returns an observable sequence that is the result of invoking the selector on a connectable observable sequence that shares a single subscription to the underlying sequence and starts with initialValue. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive immediately receive the initial value, followed by all notifications of the source from the time of the subscription on. + /// Initial value received by observers upon subscription. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// or is null. + /// + public static IObservable Publish(this IObservable source, Func, IObservable> selector, TSource initialValue) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Publish(source, selector, initialValue); + } + + #endregion + + #region + PublishLast + + + /// + /// Returns a connectable observable sequence that shares a single subscription to the underlying sequence containing only the last notification. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// A connectable observable sequence that shares a single subscription to the underlying sequence. + /// is null. + /// Subscribers will only receive the last notification of the source. + /// + public static IConnectableObservable PublishLast(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.PublishLast(source); + } + + /// + /// Returns an observable sequence that is the result of invoking the selector on a connectable observable sequence that shares a single subscription to the underlying sequence containing only the last notification. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will only receive the last notification of the source. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// or is null. + /// + public static IObservable PublishLast(this IObservable source, Func, IObservable> selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.PublishLast(source, selector); + } + + #endregion + + #region + RefCount + + + /// + /// Returns an observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Connectable observable sequence. + /// An observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. + /// is null. + public static IObservable RefCount(this IConnectableObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.RefCount(source); + } + + /// + /// Returns an observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Connectable observable sequence. + /// The time span that should be waited before possibly unsubscribing from the connectable observable. + /// An observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. + /// is null. + public static IObservable RefCount(this IConnectableObservable source, TimeSpan disconnectDelay) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (disconnectDelay < TimeSpan.Zero) + { + throw new ArgumentException("Delay cannot be less than zero", nameof(disconnectDelay)); + } + + return s_impl.RefCount(source, disconnectDelay); + } + + /// + /// Returns an observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Connectable observable sequence. + /// The time span that should be waited before possibly unsubscribing from the connectable observable. + /// The scheduler to use for delayed unsubscription. + /// An observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. + /// is null. + public static IObservable RefCount(this IConnectableObservable source, TimeSpan disconnectDelay, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (disconnectDelay < TimeSpan.Zero) + { + throw new ArgumentException("Delay cannot be less than zero", nameof(disconnectDelay)); + } + + return s_impl.RefCount(source, disconnectDelay, scheduler); + } + + /// + /// Returns an observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Connectable observable sequence. + /// The minimum number of observers required to subscribe before establishing the connection to the source. + /// An observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. + /// is null. + /// is non-positive. + public static IObservable RefCount(this IConnectableObservable source, int minObservers) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (minObservers <= 0) + { + throw new ArgumentOutOfRangeException(nameof(minObservers)); + } + + return s_impl.RefCount(source, minObservers); + } + + /// + /// Returns an observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Connectable observable sequence. + /// The minimum number of observers required to subscribe before establishing the connection to the source. + /// The time span that should be waited before possibly unsubscribing from the connectable observable. + /// An observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. + /// is null. + /// is non-positive. + public static IObservable RefCount(this IConnectableObservable source, int minObservers, TimeSpan disconnectDelay) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (disconnectDelay < TimeSpan.Zero) + { + throw new ArgumentException("Delay cannot be less than zero", nameof(disconnectDelay)); + } + if (minObservers <= 0) + { + throw new ArgumentOutOfRangeException(nameof(minObservers)); + } + + return s_impl.RefCount(source, minObservers, disconnectDelay); + } + + /// + /// Returns an observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Connectable observable sequence. + /// The minimum number of observers required to subscribe before establishing the connection to the source. + /// The time span that should be waited before possibly unsubscribing from the connectable observable. + /// The scheduler to use for delayed unsubscription. + /// An observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. + /// is null. + /// is non-positive. + public static IObservable RefCount(this IConnectableObservable source, int minObservers, TimeSpan disconnectDelay, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (disconnectDelay < TimeSpan.Zero) + { + throw new ArgumentException("Delay cannot be less than zero", nameof(disconnectDelay)); + } + if (minObservers <= 0) + { + throw new ArgumentOutOfRangeException(nameof(minObservers)); + } + + + return s_impl.RefCount(source, minObservers, disconnectDelay, scheduler); + } + + #endregion + + #region + AutoConnect + + + /// + /// Automatically connect the upstream IConnectableObservable at most once when the + /// specified number of IObservers have subscribed to this IObservable. + /// + /// The type of the elements in the source sequence. + /// Connectable observable sequence. + /// The number of observers required to subscribe before the connection to source happens, non-positive value will trigger an immediate subscription. + /// If not null, the connection's IDisposable is provided to it. + /// An observable sequence that connects to the source at most once when the given number of observers have subscribed to it. + /// is null. + public static IObservable AutoConnect(this IConnectableObservable source, int minObservers = 1, Action? onConnect = null) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.AutoConnect(source, minObservers, onConnect); + } + + #endregion + + #region + Replay + + + /// + /// Returns a connectable observable sequence that shares a single subscription to the underlying sequence replaying all notifications. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// A connectable observable sequence that shares a single subscription to the underlying sequence. + /// is null. + /// Subscribers will receive all the notifications of the source. + /// + public static IConnectableObservable Replay(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Replay(source); + } + + /// + /// Returns a connectable observable sequence that shares a single subscription to the underlying sequence replaying all notifications. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Scheduler where connected observers will be invoked on. + /// A connectable observable sequence that shares a single subscription to the underlying sequence. + /// or is null. + /// Subscribers will receive all the notifications of the source. + /// + public static IConnectableObservable Replay(this IObservable source, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Replay(source, scheduler); + } + + /// + /// Returns an observable sequence that is the result of invoking the selector on a connectable observable sequence that shares a single subscription to the underlying sequence replaying all notifications. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive all the notifications of the source. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// or is null. + /// + public static IObservable Replay(this IObservable source, Func, IObservable> selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Replay(source, selector); + } + + /// + /// Returns an observable sequence that is the result of invoking the selector on a connectable observable sequence that shares a single subscription to the underlying sequence replaying all notifications. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive all the notifications of the source. + /// Scheduler where connected observers within the selector function will be invoked on. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// or or is null. + /// + public static IObservable Replay(this IObservable source, Func, IObservable> selector, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Replay(source, selector, scheduler); + } + + /// + /// Returns a connectable observable sequence that shares a single subscription to the underlying sequence replaying notifications subject to a maximum time length for the replay buffer. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Maximum time length of the replay buffer. + /// A connectable observable sequence that shares a single subscription to the underlying sequence. + /// is null. + /// is less than TimeSpan.Zero. + /// Subscribers will receive all the notifications of the source subject to the specified replay buffer trimming policy. + /// + public static IConnectableObservable Replay(this IObservable source, TimeSpan window) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (window < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(window)); + } + + return s_impl.Replay(source, window); + } + + /// + /// Returns an observable sequence that is the result of invoking the selector on a connectable observable sequence that shares a single subscription to the underlying sequence replaying notifications subject to a maximum time length for the replay buffer. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive all the notifications of the source subject to the specified replay buffer trimming policy. + /// Maximum time length of the replay buffer. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// or is null. + /// is less than TimeSpan.Zero. + /// + public static IObservable Replay(this IObservable source, Func, IObservable> selector, TimeSpan window) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + if (window < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(window)); + } + + return s_impl.Replay(source, selector, window); + } + + /// + /// Returns a connectable observable sequence that shares a single subscription to the underlying sequence replaying notifications subject to a maximum time length for the replay buffer. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Maximum time length of the replay buffer. + /// Scheduler where connected observers will be invoked on. + /// A connectable observable sequence that shares a single subscription to the underlying sequence. + /// or is null. + /// is less than TimeSpan.Zero. + /// Subscribers will receive all the notifications of the source subject to the specified replay buffer trimming policy. + /// + public static IConnectableObservable Replay(this IObservable source, TimeSpan window, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (window < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(window)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Replay(source, window, scheduler); + } + + /// + /// Returns an observable sequence that is the result of invoking the selector on a connectable observable sequence that shares a single subscription to the underlying sequence replaying notifications subject to a maximum time length for the replay buffer. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive all the notifications of the source subject to the specified replay buffer trimming policy. + /// Maximum time length of the replay buffer. + /// Scheduler where connected observers within the selector function will be invoked on. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// or or is null. + /// is less than TimeSpan.Zero. + /// + public static IObservable Replay(this IObservable source, Func, IObservable> selector, TimeSpan window, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + if (window < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(window)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Replay(source, selector, window, scheduler); + } + + /// + /// Returns a connectable observable sequence that shares a single subscription to the underlying sequence replaying bufferSize notifications. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Maximum element count of the replay buffer. + /// Scheduler where connected observers will be invoked on. + /// A connectable observable sequence that shares a single subscription to the underlying sequence. + /// or is null. + /// is less than zero. + /// Subscribers will receive all the notifications of the source subject to the specified replay buffer trimming policy. + /// + public static IConnectableObservable Replay(this IObservable source, int bufferSize, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (bufferSize < 0) + { + throw new ArgumentOutOfRangeException(nameof(bufferSize)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Replay(source, bufferSize, scheduler); + } + + /// + /// Returns an observable sequence that is the result of invoking the selector on a connectable observable sequence that shares a single subscription to the underlying sequence replaying notifications subject to a maximum element count for the replay buffer. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive all the notifications of the source subject to the specified replay buffer trimming policy. + /// Maximum element count of the replay buffer. + /// Scheduler where connected observers within the selector function will be invoked on. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// or or is null. + /// is less than zero. + /// + public static IObservable Replay(this IObservable source, Func, IObservable> selector, int bufferSize, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + if (bufferSize < 0) + { + throw new ArgumentOutOfRangeException(nameof(bufferSize)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Replay(source, selector, bufferSize, scheduler); + } + + /// + /// Returns a connectable observable sequence that shares a single subscription to the underlying sequence replaying notifications subject to a maximum element count for the replay buffer. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Maximum element count of the replay buffer. + /// A connectable observable sequence that shares a single subscription to the underlying sequence. + /// is null. + /// is less than zero. + /// Subscribers will receive all the notifications of the source subject to the specified replay buffer trimming policy. + /// + public static IConnectableObservable Replay(this IObservable source, int bufferSize) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (bufferSize < 0) + { + throw new ArgumentOutOfRangeException(nameof(bufferSize)); + } + + return s_impl.Replay(source, bufferSize); + } + + /// + /// Returns an observable sequence that is the result of invoking the selector on a connectable observable sequence that shares a single subscription to the underlying sequence replaying notifications subject to a maximum element count for the replay buffer. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive all the notifications of the source subject to the specified replay buffer trimming policy. + /// Maximum element count of the replay buffer. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// or is null. + /// is less than zero. + /// + public static IObservable Replay(this IObservable source, Func, IObservable> selector, int bufferSize) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + if (bufferSize < 0) + { + throw new ArgumentOutOfRangeException(nameof(bufferSize)); + } + + return s_impl.Replay(source, selector, bufferSize); + } + + /// + /// Returns a connectable observable sequence that shares a single subscription to the underlying sequence replaying notifications subject to a maximum time length and element count for the replay buffer. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Maximum element count of the replay buffer. + /// Maximum time length of the replay buffer. + /// A connectable observable sequence that shares a single subscription to the underlying sequence. + /// is null. + /// is less than zero. + /// is less than TimeSpan.Zero. + /// Subscribers will receive all the notifications of the source subject to the specified replay buffer trimming policy. + /// + public static IConnectableObservable Replay(this IObservable source, int bufferSize, TimeSpan window) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (bufferSize < 0) + { + throw new ArgumentOutOfRangeException(nameof(bufferSize)); + } + + if (window < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(window)); + } + + return s_impl.Replay(source, bufferSize, window); + } + + /// + /// Returns an observable sequence that is the result of invoking the selector on a connectable observable sequence that shares a single subscription to the underlying sequence replaying notifications subject to a maximum time length and element count for the replay buffer. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive all the notifications of the source subject to the specified replay buffer trimming policy. + /// Maximum element count of the replay buffer. + /// Maximum time length of the replay buffer. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// or is null. + /// is less than zero. + /// is less than TimeSpan.Zero. + /// + public static IObservable Replay(this IObservable source, Func, IObservable> selector, int bufferSize, TimeSpan window) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + if (bufferSize < 0) + { + throw new ArgumentOutOfRangeException(nameof(bufferSize)); + } + + if (window < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(window)); + } + + return s_impl.Replay(source, selector, bufferSize, window); + } + + /// + /// Returns a connectable observable sequence that shares a single subscription to the underlying sequence replaying notifications subject to a maximum time length and element count for the replay buffer. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Maximum element count of the replay buffer. + /// Maximum time length of the replay buffer. + /// Scheduler where connected observers will be invoked on. + /// A connectable observable sequence that shares a single subscription to the underlying sequence. + /// or is null. + /// is less than zero. + /// is less than TimeSpan.Zero. + /// Subscribers will receive all the notifications of the source subject to the specified replay buffer trimming policy. + /// + public static IConnectableObservable Replay(this IObservable source, int bufferSize, TimeSpan window, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (bufferSize < 0) + { + throw new ArgumentOutOfRangeException(nameof(bufferSize)); + } + + if (window < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(window)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Replay(source, bufferSize, window, scheduler); + } + + /// + /// Returns an observable sequence that is the result of invoking the selector on a connectable observable sequence that shares a single subscription to the underlying sequence replaying notifications subject to a maximum time length and element count for the replay buffer. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive all the notifications of the source subject to the specified replay buffer trimming policy. + /// Maximum element count of the replay buffer. + /// Maximum time length of the replay buffer. + /// Scheduler where connected observers within the selector function will be invoked on. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// or or is null. + /// is less than zero. + /// is less than TimeSpan.Zero. + /// + public static IObservable Replay(this IObservable source, Func, IObservable> selector, int bufferSize, TimeSpan window, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + if (bufferSize < 0) + { + throw new ArgumentOutOfRangeException(nameof(bufferSize)); + } + + if (window < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(window)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Replay(source, selector, bufferSize, window, scheduler); + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable.Blocking.cs b/LibExternal/System.Reactive/Linq/Observable.Blocking.cs new file mode 100644 index 0000000..ceb3682 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable.Blocking.cs @@ -0,0 +1,571 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace System.Reactive.Linq +{ + public static partial class Observable + { + #region + Chunkify + + + /// + /// Produces an enumerable sequence of consecutive (possibly empty) chunks of the source sequence. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// The enumerable sequence that returns consecutive (possibly empty) chunks upon each iteration. + /// is null. + public static IEnumerable> Chunkify(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Chunkify(source); + } + + #endregion + + #region + Collect + + + /// + /// Produces an enumerable sequence that returns elements collected/aggregated from the source sequence between consecutive iterations. + /// + /// The type of the elements in the source sequence. + /// The type of the elements produced by the merge operation during collection. + /// Source observable sequence. + /// Factory to create a new collector object. + /// Merges a sequence element with the current collector. + /// The enumerable sequence that returns collected/aggregated elements from the source sequence upon each iteration. + /// or or is null. + public static IEnumerable Collect(this IObservable source, Func newCollector, Func merge) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (newCollector == null) + { + throw new ArgumentNullException(nameof(newCollector)); + } + + if (merge == null) + { + throw new ArgumentNullException(nameof(merge)); + } + + return s_impl.Collect(source, newCollector, merge); + } + + /// + /// Produces an enumerable sequence that returns elements collected/aggregated from the source sequence between consecutive iterations. + /// + /// The type of the elements in the source sequence. + /// The type of the elements produced by the merge operation during collection. + /// Source observable sequence. + /// Factory to create the initial collector object. + /// Merges a sequence element with the current collector. + /// Factory to replace the current collector by a new collector. + /// The enumerable sequence that returns collected/aggregated elements from the source sequence upon each iteration. + /// or or or is null. + public static IEnumerable Collect(this IObservable source, Func getInitialCollector, Func merge, Func getNewCollector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (getInitialCollector == null) + { + throw new ArgumentNullException(nameof(getInitialCollector)); + } + + if (merge == null) + { + throw new ArgumentNullException(nameof(merge)); + } + + if (getNewCollector == null) + { + throw new ArgumentNullException(nameof(getNewCollector)); + } + + return s_impl.Collect(source, getInitialCollector, merge, getNewCollector); + } + + #endregion + + #region + First + + + /// + /// Returns the first element of an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// The first element in the observable sequence. + /// is null. + /// The source sequence is empty. + /// + [Obsolete(Constants_Linq.UseAsync)] + public static TSource First(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.First(source); + } + + /// + /// Returns the first element of an observable sequence that satisfies the condition in the predicate. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// A predicate function to evaluate for elements in the source sequence. + /// The first element in the observable sequence that satisfies the condition in the predicate. + /// or is null. + /// No element satisfies the condition in the predicate. -or- The source sequence is empty. + /// + [Obsolete(Constants_Linq.UseAsync)] + public static TSource First(this IObservable source, Func predicate) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return s_impl.First(source, predicate); + } + + #endregion + + #region + FirstOrDefault + + + /// + /// Returns the first element of an observable sequence, or a default value if no such element exists. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// The first element in the observable sequence, or a default value if no such element exists. + /// is null. + /// + [Obsolete(Constants_Linq.UseAsync)] + [return: MaybeNull] + public static TSource FirstOrDefault(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.FirstOrDefault(source); + } + + /// + /// Returns the first element of an observable sequence that satisfies the condition in the predicate, or a default value if no such element exists. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// A predicate function to evaluate for elements in the source sequence. + /// The first element in the observable sequence that satisfies the condition in the predicate, or a default value if no such element exists. + /// or is null. + /// + [Obsolete(Constants_Linq.UseAsync)] + [return: MaybeNull] + public static TSource FirstOrDefault(this IObservable source, Func predicate) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return s_impl.FirstOrDefault(source, predicate); + } + + #endregion + + #region + ForEach + + + /// + /// Invokes an action for each element in the observable sequence, and blocks until the sequence is terminated. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Action to invoke for each element in the observable sequence. + /// or is null. + /// Because of its blocking nature, this operator is mainly used for testing. + [Obsolete(Constants_Linq.UseAsync)] + public static void ForEach(this IObservable source, Action onNext) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + s_impl.ForEach(source, onNext); + } + + /// + /// Invokes an action for each element in the observable sequence, incorporating the element's index, and blocks until the sequence is terminated. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Action to invoke for each element in the observable sequence. + /// or is null. + /// Because of its blocking nature, this operator is mainly used for testing. + [Obsolete(Constants_Linq.UseAsync)] + public static void ForEach(this IObservable source, Action onNext) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + s_impl.ForEach(source, onNext); + } + + #endregion + + #region + GetEnumerator + + + /// + /// Returns an enumerator that enumerates all values of the observable sequence. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to get an enumerator for. + /// The enumerator that can be used to enumerate over the elements in the observable sequence. + /// is null. + public static IEnumerator GetEnumerator(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.GetEnumerator(source); + } + + #endregion + + #region + Last + + + /// + /// Returns the last element of an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// The last element in the observable sequence. + /// is null. + /// The source sequence is empty. + /// + [Obsolete(Constants_Linq.UseAsync)] + public static TSource Last(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Last(source); + } + + /// + /// Returns the last element of an observable sequence that satisfies the condition in the predicate. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// A predicate function to evaluate for elements in the source sequence. + /// The last element in the observable sequence that satisfies the condition in the predicate. + /// or is null. + /// No element satisfies the condition in the predicate. -or- The source sequence is empty. + /// + [Obsolete(Constants_Linq.UseAsync)] + public static TSource Last(this IObservable source, Func predicate) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return s_impl.Last(source, predicate); + } + + #endregion + + #region + LastOrDefault + + + /// + /// Returns the last element of an observable sequence, or a default value if no such element exists. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// The last element in the observable sequence, or a default value if no such element exists. + /// is null. + /// + [Obsolete(Constants_Linq.UseAsync)] + [return: MaybeNull] + public static TSource LastOrDefault(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.LastOrDefault(source); + } + + /// + /// Returns the last element of an observable sequence that satisfies the condition in the predicate, or a default value if no such element exists. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// A predicate function to evaluate for elements in the source sequence. + /// The last element in the observable sequence that satisfies the condition in the predicate, or a default value if no such element exists. + /// or is null. + /// + [Obsolete(Constants_Linq.UseAsync)] + [return: MaybeNull] + public static TSource LastOrDefault(this IObservable source, Func predicate) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return s_impl.LastOrDefault(source, predicate); + } + + #endregion + + #region + Latest + + + /// + /// Returns an enumerable sequence whose enumeration returns the latest observed element in the source observable sequence. + /// Enumerators on the resulting sequence will never produce the same element repeatedly, and will block until the next element becomes available. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// The enumerable sequence that returns the last sampled element upon each iteration and subsequently blocks until the next element in the observable source sequence becomes available. + public static IEnumerable Latest(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Latest(source); + } + + #endregion + + #region + MostRecent + + + /// + /// Returns an enumerable sequence whose enumeration returns the most recently observed element in the source observable sequence, using the specified initial value in case no element has been sampled yet. + /// Enumerators on the resulting sequence never block and can produce the same element repeatedly. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// Initial value that will be yielded by the enumerable sequence if no element has been sampled yet. + /// The enumerable sequence that returns the last sampled element upon each iteration. + /// is null. + public static IEnumerable MostRecent(this IObservable source, TSource initialValue) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.MostRecent(source, initialValue); + } + + #endregion + + #region + Next + + + /// + /// Returns an enumerable sequence whose enumeration blocks until the next element in the source observable sequence becomes available. + /// Enumerators on the resulting sequence will block until the next element becomes available. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// The enumerable sequence that blocks upon each iteration until the next element in the observable source sequence becomes available. + /// is null. + public static IEnumerable Next(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Next(source); + } + + #endregion + + #region + Single + + + /// + /// Returns the only element of an observable sequence, and throws an exception if there is not exactly one element in the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// The single element in the observable sequence. + /// is null. + /// The source sequence contains more than one element. -or- The source sequence is empty. + /// + [Obsolete(Constants_Linq.UseAsync)] +#pragma warning disable CA1720 // (Identifier contains type name.) Single is long-established as a LINQ method name. + public static TSource Single(this IObservable source) +#pragma warning restore CA1720 + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Single(source); + } + + /// + /// Returns the only element of an observable sequence that satisfies the condition in the predicate, and throws an exception if there is not exactly one element matching the predicate in the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// A predicate function to evaluate for elements in the source sequence. + /// The single element in the observable sequence that satisfies the condition in the predicate. + /// or is null. + /// No element satisfies the condition in the predicate. -or- More than one element satisfies the condition in the predicate. -or- The source sequence is empty. + /// + [Obsolete(Constants_Linq.UseAsync)] +#pragma warning disable CA1720 // (Identifier contains type name.) Single is long-established as a LINQ method name. + public static TSource Single(this IObservable source, Func predicate) +#pragma warning restore CA1720 + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return s_impl.Single(source, predicate); + } + + #endregion + + #region + SingleOrDefault + + + /// + /// Returns the only element of an observable sequence, or a default value if the observable sequence is empty; this method throws an exception if there is more than one element in the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// The single element in the observable sequence, or a default value if no such element exists. + /// is null. + /// The source sequence contains more than one element. + /// + [Obsolete(Constants_Linq.UseAsync)] + [return: MaybeNull] + public static TSource SingleOrDefault(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.SingleOrDefault(source); + } + + /// + /// Returns the only element of an observable sequence that satisfies the condition in the predicate, or a default value if no such element exists; this method throws an exception if there is more than one element matching the predicate in the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// A predicate function to evaluate for elements in the source sequence. + /// The single element in the observable sequence that satisfies the condition in the predicate, or a default value if no such element exists. + /// or is null. + /// The sequence contains more than one element that satisfies the condition in the predicate. + /// + [Obsolete(Constants_Linq.UseAsync)] + [return: MaybeNull] + public static TSource SingleOrDefault(this IObservable source, Func predicate) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return s_impl.SingleOrDefault(source, predicate); + } + + #endregion + + #region + Wait + + + /// + /// Waits for the observable sequence to complete and returns the last element of the sequence. + /// If the sequence terminates with an OnError notification, the exception is thrown. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// The last element in the observable sequence. + /// is null. + /// The source sequence is empty. + public static TSource Wait(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Wait(source); + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable.Concurrency.cs b/LibExternal/System.Reactive/Linq/Observable.Concurrency.cs new file mode 100644 index 0000000..a261c87 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable.Concurrency.cs @@ -0,0 +1,180 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Threading; + +namespace System.Reactive.Linq +{ + public static partial class Observable + { + #region + ObserveOn + + + /// + /// Wraps the source sequence in order to run its observer callbacks on the specified scheduler. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Scheduler to notify observers on. + /// The source sequence whose observations happen on the specified scheduler. + /// or is null. + /// + /// This only invokes observer callbacks on a scheduler. In case the subscription and/or unsubscription actions have side-effects + /// that require to be run on a scheduler, use . + /// + public static IObservable ObserveOn(this IObservable source, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ObserveOn(source, scheduler); + } + + /// + /// Wraps the source sequence in order to run its observer callbacks on the specified synchronization context. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Synchronization context to notify observers on. + /// The source sequence whose observations happen on the specified synchronization context. + /// or is null. + /// + /// This only invokes observer callbacks on a synchronization context. In case the subscription and/or unsubscription actions have side-effects + /// that require to be run on a synchronization context, use . + /// + public static IObservable ObserveOn(this IObservable source, SynchronizationContext context) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + return s_impl.ObserveOn(source, context); + } + + #endregion + + #region + SubscribeOn + + + /// + /// Wraps the source sequence in order to run its subscription and unsubscription logic on the specified scheduler. This operation is not commonly used; + /// see the remarks section for more information on the distinction between SubscribeOn and ObserveOn. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Scheduler to perform subscription and unsubscription actions on. + /// The source sequence whose subscriptions and unsubscriptions happen on the specified scheduler. + /// or is null. + /// + /// This only performs the side-effects of subscription and unsubscription on the specified scheduler. In order to invoke observer + /// callbacks on a scheduler, use . + /// + public static IObservable SubscribeOn(this IObservable source, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.SubscribeOn(source, scheduler); + } + + /// + /// Wraps the source sequence in order to run its subscription and unsubscription logic on the specified synchronization context. This operation is not commonly used; + /// see the remarks section for more information on the distinction between SubscribeOn and ObserveOn. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Synchronization context to perform subscription and unsubscription actions on. + /// The source sequence whose subscriptions and unsubscriptions happen on the specified synchronization context. + /// or is null. + /// + /// This only performs the side-effects of subscription and unsubscription on the specified synchronization context. In order to invoke observer + /// callbacks on a synchronization context, use . + /// + public static IObservable SubscribeOn(this IObservable source, SynchronizationContext context) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + return s_impl.SubscribeOn(source, context); + } + + #endregion + + #region + Synchronize + + + /// + /// Synchronizes the observable sequence such that observer notifications cannot be delivered concurrently. + /// This overload is useful to "fix" an observable sequence that exhibits concurrent callbacks on individual observers, which is invalid behavior for the query processor. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// The source sequence whose outgoing calls to observers are synchronized. + /// is null. + /// + /// It's invalid behavior - according to the observer grammar - for a sequence to exhibit concurrent callbacks on a given observer. + /// This operator can be used to "fix" a source that doesn't conform to this rule. + /// + public static IObservable Synchronize(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Synchronize(source); + } + + /// + /// Synchronizes the observable sequence such that observer notifications cannot be delivered concurrently, using the specified gate object. + /// This overload is useful when writing n-ary query operators, in order to prevent concurrent callbacks from different sources by synchronizing on a common gate object. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Gate object to synchronize each observer call on. + /// The source sequence whose outgoing calls to observers are synchronized on the given gate object. + /// or is null. + public static IObservable Synchronize(this IObservable source, object gate) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (gate == null) + { + throw new ArgumentNullException(nameof(gate)); + } + + return s_impl.Synchronize(source, gate); + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable.Conversions.cs b/LibExternal/System.Reactive/Linq/Observable.Conversions.cs new file mode 100644 index 0000000..4ce3db8 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable.Conversions.cs @@ -0,0 +1,191 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Concurrency; + +namespace System.Reactive.Linq +{ + public static partial class Observable + { + #region + Subscribe + + + /// + /// Subscribes an observer to an enumerable sequence. + /// + /// The type of the elements in the source sequence. + /// Enumerable sequence to subscribe to. + /// Observer that will receive notifications from the enumerable sequence. + /// Disposable object that can be used to unsubscribe the observer from the enumerable + /// or is null. + public static IDisposable Subscribe(this IEnumerable source, IObserver observer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + return s_impl.Subscribe(source, observer); + } + + /// + /// Subscribes an observer to an enumerable sequence, using the specified scheduler to run the enumeration loop. + /// + /// The type of the elements in the source sequence. + /// Enumerable sequence to subscribe to. + /// Observer that will receive notifications from the enumerable sequence. + /// Scheduler to perform the enumeration on. + /// Disposable object that can be used to unsubscribe the observer from the enumerable + /// or or is null. + public static IDisposable Subscribe(this IEnumerable source, IObserver observer, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Subscribe(source, observer, scheduler); + } + + #endregion + + #region + ToEnumerable + + + /// + /// Converts an observable sequence to an enumerable sequence. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to convert to an enumerable sequence. + /// The enumerable sequence containing the elements in the observable sequence. + /// is null. + public static IEnumerable ToEnumerable(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.ToEnumerable(source); + } + + #endregion + + #region ToEvent + + /// + /// Exposes an observable sequence as an object with an -based .NET event. + /// + /// Observable source sequence. + /// The event source object. + /// is null. + public static IEventSource ToEvent(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.ToEvent(source); + } + + /// + /// Exposes an observable sequence as an object with an -based .NET event. + /// + /// The type of the elements in the source sequence. + /// Observable source sequence. + /// The event source object. + /// is null. + public static IEventSource ToEvent(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.ToEvent(source); + } + + #endregion + + #region ToEventPattern + + /// + /// Exposes an observable sequence as an object with a .NET event, conforming to the standard .NET event pattern. + /// + /// The type of the event data generated by the event. + /// Observable source sequence. + /// The event source object. + /// is null. + public static IEventPatternSource ToEventPattern(this IObservable> source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.ToEventPattern(source); + } + + #endregion + + #region + ToObservable + + + /// + /// Converts an enumerable sequence to an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Enumerable sequence to convert to an observable sequence. + /// The observable sequence whose elements are pulled from the given enumerable sequence. + /// is null. + public static IObservable ToObservable(this IEnumerable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.ToObservable(source); + } + + /// + /// Converts an enumerable sequence to an observable sequence, using the specified scheduler to run the enumeration loop. + /// + /// The type of the elements in the source sequence. + /// Enumerable sequence to convert to an observable sequence. + /// Scheduler to run the enumeration of the input sequence on. + /// The observable sequence whose elements are pulled from the given enumerable sequence. + /// or is null. + public static IObservable ToObservable(this IEnumerable source, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ToObservable(source, scheduler); + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable.Creation.cs b/LibExternal/System.Reactive/Linq/Observable.Creation.cs new file mode 100644 index 0000000..af64b0c --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable.Creation.cs @@ -0,0 +1,748 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Reactive.Linq +{ + public static partial class Observable + { + #region + Create + + + /// + /// Creates an observable sequence from a specified Subscribe method implementation. + /// + /// The type of the elements in the produced sequence. + /// Implementation of the resulting observable sequence's Subscribe method. + /// The observable sequence with the specified implementation for the Subscribe method. + /// is null. + /// + /// Use of this operator is preferred over manual implementation of the interface. In case + /// you need a type implementing rather than an anonymous implementation, consider using + /// the abstract base class. + /// + public static IObservable Create(Func, IDisposable> subscribe) + { + if (subscribe == null) + { + throw new ArgumentNullException(nameof(subscribe)); + } + + return s_impl.Create(subscribe); + } + + /// + /// Creates an observable sequence from a specified Subscribe method implementation. + /// + /// The type of the elements in the produced sequence. + /// Implementation of the resulting observable sequence's Subscribe method, returning an Action delegate that will be wrapped in an IDisposable. + /// The observable sequence with the specified implementation for the Subscribe method. + /// is null. + /// + /// Use of this operator is preferred over manual implementation of the interface. In case + /// you need a type implementing rather than an anonymous implementation, consider using + /// the abstract base class. + /// + public static IObservable Create(Func, Action> subscribe) + { + if (subscribe == null) + { + throw new ArgumentNullException(nameof(subscribe)); + } + + return s_impl.Create(subscribe); + } + + #endregion + + #region + CreateAsync + + + /// + /// Creates an observable sequence from a specified cancellable asynchronous Subscribe method. + /// The CancellationToken passed to the asynchronous Subscribe method is tied to the returned disposable subscription, allowing best-effort cancellation. + /// + /// The type of the elements in the produced sequence. + /// Asynchronous method used to produce elements. + /// The observable sequence surfacing the elements produced by the asynchronous method. + /// is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + /// When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous subscribe function will be signaled. + public static IObservable Create(Func, CancellationToken, Task> subscribeAsync) + { + if (subscribeAsync == null) + { + throw new ArgumentNullException(nameof(subscribeAsync)); + } + + return s_impl.Create(subscribeAsync); + } + + /// + /// Creates an observable sequence from a specified asynchronous Subscribe method. + /// + /// The type of the elements in the produced sequence. + /// Asynchronous method used to produce elements. + /// The observable sequence surfacing the elements produced by the asynchronous method. + /// is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + public static IObservable Create(Func, Task> subscribeAsync) + { + if (subscribeAsync == null) + { + throw new ArgumentNullException(nameof(subscribeAsync)); + } + + return s_impl.Create(subscribeAsync); + } + + /// + /// Creates an observable sequence from a specified cancellable asynchronous Subscribe method. + /// The CancellationToken passed to the asynchronous Subscribe method is tied to the returned disposable subscription, allowing best-effort cancellation. + /// + /// The type of the elements in the produced sequence. + /// Asynchronous method used to implement the resulting sequence's Subscribe method. + /// The observable sequence with the specified implementation for the Subscribe method. + /// is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + /// When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous subscribe function will be signaled. + public static IObservable Create(Func, CancellationToken, Task> subscribeAsync) + { + if (subscribeAsync == null) + { + throw new ArgumentNullException(nameof(subscribeAsync)); + } + + return s_impl.Create(subscribeAsync); + } + + /// + /// Creates an observable sequence from a specified asynchronous Subscribe method. + /// + /// The type of the elements in the produced sequence. + /// Asynchronous method used to implement the resulting sequence's Subscribe method. + /// The observable sequence with the specified implementation for the Subscribe method. + /// is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + public static IObservable Create(Func, Task> subscribeAsync) + { + if (subscribeAsync == null) + { + throw new ArgumentNullException(nameof(subscribeAsync)); + } + + return s_impl.Create(subscribeAsync); + } + + /// + /// Creates an observable sequence from a specified cancellable asynchronous Subscribe method. + /// The CancellationToken passed to the asynchronous Subscribe method is tied to the returned disposable subscription, allowing best-effort cancellation. + /// + /// The type of the elements in the produced sequence. + /// Asynchronous method used to implement the resulting sequence's Subscribe method, returning an Action delegate that will be wrapped in an IDisposable. + /// The observable sequence with the specified implementation for the Subscribe method. + /// is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + /// When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous subscribe function will be signaled. + public static IObservable Create(Func, CancellationToken, Task> subscribeAsync) + { + if (subscribeAsync == null) + { + throw new ArgumentNullException(nameof(subscribeAsync)); + } + + return s_impl.Create(subscribeAsync); + } + + /// + /// Creates an observable sequence from a specified asynchronous Subscribe method. + /// + /// The type of the elements in the produced sequence. + /// Asynchronous method used to implement the resulting sequence's Subscribe method, returning an Action delegate that will be wrapped in an IDisposable. + /// The observable sequence with the specified implementation for the Subscribe method. + /// is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + public static IObservable Create(Func, Task> subscribeAsync) + { + if (subscribeAsync == null) + { + throw new ArgumentNullException(nameof(subscribeAsync)); + } + + return s_impl.Create(subscribeAsync); + } + + #endregion + + #region + Defer + + + /// + /// Returns an observable sequence that invokes the specified factory function whenever a new observer subscribes. + /// + /// The type of the elements in the sequence returned by the factory function, and in the resulting sequence. + /// Observable factory function to invoke for each observer that subscribes to the resulting sequence. + /// An observable sequence whose observers trigger an invocation of the given observable factory function. + /// is null. + public static IObservable Defer(Func> observableFactory) + { + if (observableFactory == null) + { + throw new ArgumentNullException(nameof(observableFactory)); + } + + return s_impl.Defer(observableFactory); + } + + #endregion + + #region + DeferAsync + + + /// + /// Returns an observable sequence that starts the specified asynchronous factory function whenever a new observer subscribes. + /// + /// The type of the elements in the sequence returned by the factory function, and in the resulting sequence. + /// Asynchronous factory function to start for each observer that subscribes to the resulting sequence. + /// An observable sequence whose observers trigger the given asynchronous observable factory function to be started. + /// is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + public static IObservable Defer(Func>> observableFactoryAsync) + { + return Defer(observableFactoryAsync, ignoreExceptionsAfterUnsubscribe: false); + } + + /// + /// Returns an observable sequence that starts the specified asynchronous factory function whenever a new observer subscribes. + /// + /// The type of the elements in the sequence returned by the factory function, and in the resulting sequence. + /// Asynchronous factory function to start for each observer that subscribes to the resulting sequence. + /// + /// If true, exceptions that occur after cancellation has been initiated by unsubscribing from the observable + /// this method returns will be handled and silently ignored. If false, they will go unobserved, meaning they + /// will eventually emerge through . + /// + /// An observable sequence whose observers trigger the given asynchronous observable factory function to be started. + /// is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + public static IObservable Defer(Func>> observableFactoryAsync, bool ignoreExceptionsAfterUnsubscribe) + { + if (observableFactoryAsync == null) + { + throw new ArgumentNullException(nameof(observableFactoryAsync)); + } + + return s_impl.Defer(observableFactoryAsync, ignoreExceptionsAfterUnsubscribe); + } + + /// + /// Returns an observable sequence that starts the specified cancellable asynchronous factory function whenever a new observer subscribes. + /// The CancellationToken passed to the asynchronous factory function is tied to the returned disposable subscription, allowing best-effort cancellation. + /// + /// The type of the elements in the sequence returned by the factory function, and in the resulting sequence. + /// Asynchronous factory function to start for each observer that subscribes to the resulting sequence. + /// An observable sequence whose observers trigger the given asynchronous observable factory function to be started. + /// is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + /// When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous observable factory function will be signaled. + public static IObservable DeferAsync(Func>> observableFactoryAsync) + { + return DeferAsync(observableFactoryAsync, ignoreExceptionsAfterUnsubscribe: false); + } + + /// + /// Returns an observable sequence that starts the specified cancellable asynchronous factory function whenever a new observer subscribes. + /// The CancellationToken passed to the asynchronous factory function is tied to the returned disposable subscription, allowing best-effort cancellation. + /// + /// The type of the elements in the sequence returned by the factory function, and in the resulting sequence. + /// Asynchronous factory function to start for each observer that subscribes to the resulting sequence. + /// + /// If true, exceptions that occur after cancellation has been initiated by unsubscribing from the observable + /// this method returns will be handled and silently ignored. If false, they will go unobserved, meaning they + /// will eventually emerge through . + /// + /// An observable sequence whose observers trigger the given asynchronous observable factory function to be started. + /// is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + /// When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous observable factory function will be signaled. + public static IObservable DeferAsync(Func>> observableFactoryAsync, bool ignoreExceptionsAfterUnsubscribe) + { + if (observableFactoryAsync == null) + { + throw new ArgumentNullException(nameof(observableFactoryAsync)); + } + + return s_impl.Defer(observableFactoryAsync, ignoreExceptionsAfterUnsubscribe); + } + + #endregion + + #region + Empty + + + /// + /// Returns an empty observable sequence. + /// + /// The type used for the type parameter of the resulting sequence. + /// An observable sequence with no elements. + public static IObservable Empty() + { + return s_impl.Empty(); + } + + /// + /// Returns an empty observable sequence. + /// + /// The type used for the type parameter of the resulting sequence. + /// Object solely used to infer the type of the type parameter. This parameter is typically used when creating a sequence of anonymously typed elements. + /// An observable sequence with no elements. +#pragma warning disable IDE0060 // (Remove unused parameter.) Required for type inference + public static IObservable Empty(TResult witness) +#pragma warning restore IDE0060 + { + return s_impl.Empty(); // Pure inference - no specialized target method. + } + + /// + /// Returns an empty observable sequence, using the specified scheduler to send out the single OnCompleted message. + /// + /// The type used for the type parameter of the resulting sequence. + /// Scheduler to send the termination call on. + /// An observable sequence with no elements. + /// is null. + public static IObservable Empty(IScheduler scheduler) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Empty(scheduler); + } + + /// + /// Returns an empty observable sequence, using the specified scheduler to send out the single OnCompleted message. + /// + /// The type used for the type parameter of the resulting sequence. + /// Scheduler to send the termination call on. + /// Object solely used to infer the type of the type parameter. This parameter is typically used when creating a sequence of anonymously typed elements. + /// An observable sequence with no elements. + /// is null. +#pragma warning disable IDE0060 // (Remove unused parameter.) Required for type inference + public static IObservable Empty(IScheduler scheduler, TResult witness) +#pragma warning restore IDE0060 + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Empty(scheduler); // Pure inference - no specialized target method. + } + + #endregion + + #region + Generate + + + /// + /// Generates an observable sequence by running a state-driven loop producing the sequence's elements. + /// + /// The type of the state used in the generator loop. + /// The type of the elements in the produced sequence. + /// Initial state. + /// Condition to terminate generation (upon returning false). + /// Iteration step function. + /// Selector function for results produced in the sequence. + /// The generated sequence. + /// or or is null. + public static IObservable Generate(TState initialState, Func condition, Func iterate, Func resultSelector) + { + if (condition == null) + { + throw new ArgumentNullException(nameof(condition)); + } + + if (iterate == null) + { + throw new ArgumentNullException(nameof(iterate)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.Generate(initialState, condition, iterate, resultSelector); + } + + /// + /// Generates an observable sequence by running a state-driven loop producing the sequence's elements, using the specified scheduler to send out observer messages. + /// + /// The type of the state used in the generator loop. + /// The type of the elements in the produced sequence. + /// Initial state. + /// Condition to terminate generation (upon returning false). + /// Iteration step function. + /// Selector function for results produced in the sequence. + /// Scheduler on which to run the generator loop. + /// The generated sequence. + /// or or or is null. + public static IObservable Generate(TState initialState, Func condition, Func iterate, Func resultSelector, IScheduler scheduler) + { + if (condition == null) + { + throw new ArgumentNullException(nameof(condition)); + } + + if (iterate == null) + { + throw new ArgumentNullException(nameof(iterate)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Generate(initialState, condition, iterate, resultSelector, scheduler); + } + + #endregion + + #region + Never + + + /// + /// Returns a non-terminating observable sequence, which can be used to denote an infinite duration (e.g. when using reactive joins). + /// + /// The type used for the type parameter of the resulting sequence. + /// An observable sequence whose observers will never get called. + public static IObservable Never() + { + return s_impl.Never(); + } + + /// + /// Returns a non-terminating observable sequence, which can be used to denote an infinite duration (e.g. when using reactive joins). + /// + /// The type used for the type parameter of the resulting sequence. + /// Object solely used to infer the type of the type parameter. This parameter is typically used when creating a sequence of anonymously typed elements. + /// An observable sequence whose observers will never get called. +#pragma warning disable IDE0060 // (Remove unused parameter.) Required for type inference + public static IObservable Never(TResult witness) +#pragma warning restore IDE0060 + { + return s_impl.Never(); // Pure inference - no specialized target method. + } + + #endregion + + #region + Range + + + /// + /// Generates an observable sequence of integral numbers within a specified range. + /// + /// The value of the first integer in the sequence. + /// The number of sequential integers to generate. + /// An observable sequence that contains a range of sequential integral numbers. + /// is less than zero. -or- + - 1 is larger than . + public static IObservable Range(int start, int count) + { + var max = (long)start + count - 1; + if (count < 0 || max > int.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + return s_impl.Range(start, count); + } + + /// + /// Generates an observable sequence of integral numbers within a specified range, using the specified scheduler to send out observer messages. + /// + /// The value of the first integer in the sequence. + /// The number of sequential integers to generate. + /// Scheduler to run the generator loop on. + /// An observable sequence that contains a range of sequential integral numbers. + /// is less than zero. -or- + - 1 is larger than . + /// is null. + public static IObservable Range(int start, int count, IScheduler scheduler) + { + var max = (long)start + count - 1; + if (count < 0 || max > int.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Range(start, count, scheduler); + } + + #endregion + + #region + Repeat + + + /// + /// Generates an observable sequence that repeats the given element infinitely. + /// + /// The type of the element that will be repeated in the produced sequence. + /// Element to repeat. + /// An observable sequence that repeats the given element infinitely. + public static IObservable Repeat(TResult value) + { + return s_impl.Repeat(value); + } + + /// + /// Generates an observable sequence that repeats the given element infinitely, using the specified scheduler to send out observer messages. + /// + /// The type of the element that will be repeated in the produced sequence. + /// Element to repeat. + /// Scheduler to run the producer loop on. + /// An observable sequence that repeats the given element infinitely. + /// is null. + public static IObservable Repeat(TResult value, IScheduler scheduler) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Repeat(value, scheduler); + } + + /// + /// Generates an observable sequence that repeats the given element the specified number of times. + /// + /// The type of the element that will be repeated in the produced sequence. + /// Element to repeat. + /// Number of times to repeat the element. + /// An observable sequence that repeats the given element the specified number of times. + /// is less than zero. + public static IObservable Repeat(TResult value, int repeatCount) + { + if (repeatCount < 0) + { + throw new ArgumentOutOfRangeException(nameof(repeatCount)); + } + + return s_impl.Repeat(value, repeatCount); + } + + /// + /// Generates an observable sequence that repeats the given element the specified number of times, using the specified scheduler to send out observer messages. + /// + /// The type of the element that will be repeated in the produced sequence. + /// Element to repeat. + /// Number of times to repeat the element. + /// Scheduler to run the producer loop on. + /// An observable sequence that repeats the given element the specified number of times. + /// is less than zero. + /// is null. + public static IObservable Repeat(TResult value, int repeatCount, IScheduler scheduler) + { + if (repeatCount < 0) + { + throw new ArgumentOutOfRangeException(nameof(repeatCount)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Repeat(value, repeatCount, scheduler); + } + + #endregion + + #region + Return + + + /// + /// Returns an observable sequence that contains a single element. + /// + /// The type of the element that will be returned in the produced sequence. + /// Single element in the resulting observable sequence. + /// An observable sequence containing the single specified element. + public static IObservable Return(TResult value) + { + return s_impl.Return(value); + } + + /// + /// Returns an observable sequence that contains a single element, using the specified scheduler to send out observer messages. + /// + /// The type of the element that will be returned in the produced sequence. + /// Single element in the resulting observable sequence. + /// Scheduler to send the single element on. + /// An observable sequence containing the single specified element. + /// is null. + public static IObservable Return(TResult value, IScheduler scheduler) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Return(value, scheduler); + } + + #endregion + + #region + Throw + + + /// + /// Returns an observable sequence that terminates with an exception. + /// + /// The type used for the type parameter of the resulting sequence. + /// Exception object used for the sequence's termination. + /// The observable sequence that terminates exceptionally with the specified exception object. + /// is null. + public static IObservable Throw(Exception exception) + { + if (exception == null) + { + throw new ArgumentNullException(nameof(exception)); + } + + return s_impl.Throw(exception); + } + + /// + /// Returns an observable sequence that terminates with an exception. + /// + /// The type used for the type parameter of the resulting sequence. + /// Exception object used for the sequence's termination. + /// Object solely used to infer the type of the type parameter. This parameter is typically used when creating a sequence of anonymously typed elements. + /// The observable sequence that terminates exceptionally with the specified exception object. + /// is null. +#pragma warning disable IDE0060 // (Remove unused parameter.) Required for type inference + public static IObservable Throw(Exception exception, TResult witness) +#pragma warning restore IDE0060 + { + if (exception == null) + { + throw new ArgumentNullException(nameof(exception)); + } + + return s_impl.Throw(exception); // Pure inference - no specialized target method. + } + + /// + /// Returns an observable sequence that terminates with an exception, using the specified scheduler to send out the single OnError message. + /// + /// The type used for the type parameter of the resulting sequence. + /// Exception object used for the sequence's termination. + /// Scheduler to send the exceptional termination call on. + /// The observable sequence that terminates exceptionally with the specified exception object. + /// or is null. + public static IObservable Throw(Exception exception, IScheduler scheduler) + { + if (exception == null) + { + throw new ArgumentNullException(nameof(exception)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Throw(exception, scheduler); + } + + /// + /// Returns an observable sequence that terminates with an exception, using the specified scheduler to send out the single OnError message. + /// + /// The type used for the type parameter of the resulting sequence. + /// Exception object used for the sequence's termination. + /// Scheduler to send the exceptional termination call on. + /// Object solely used to infer the type of the type parameter. This parameter is typically used when creating a sequence of anonymously typed elements. + /// The observable sequence that terminates exceptionally with the specified exception object. + /// or is null. +#pragma warning disable IDE0060 // (Remove unused parameter.) Required for type inference + public static IObservable Throw(Exception exception, IScheduler scheduler, TResult witness) +#pragma warning restore IDE0060 + { + if (exception == null) + { + throw new ArgumentNullException(nameof(exception)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Throw(exception, scheduler); // Pure inference - no specialized target method. + } + + #endregion + + #region + Using + + + /// + /// Constructs an observable sequence that depends on a resource object, whose lifetime is tied to the resulting observable sequence's lifetime. + /// + /// The type of the elements in the produced sequence. + /// The type of the resource used during the generation of the resulting sequence. Needs to implement . + /// Factory function to obtain a resource object. + /// Factory function to obtain an observable sequence that depends on the obtained resource. + /// An observable sequence whose lifetime controls the lifetime of the dependent resource object. + /// or is null. + public static IObservable Using(Func resourceFactory, Func> observableFactory) where TResource : IDisposable + { + if (resourceFactory == null) + { + throw new ArgumentNullException(nameof(resourceFactory)); + } + + if (observableFactory == null) + { + throw new ArgumentNullException(nameof(observableFactory)); + } + + return s_impl.Using(resourceFactory, observableFactory); + } + + #endregion + + #region + UsingAsync + + + /// + /// Constructs an observable sequence that depends on a resource object, whose lifetime is tied to the resulting observable sequence's lifetime. The resource is obtained and used through asynchronous methods. + /// The CancellationToken passed to the asynchronous methods is tied to the returned disposable subscription, allowing best-effort cancellation at any stage of the resource acquisition or usage. + /// + /// The type of the elements in the produced sequence. + /// The type of the resource used during the generation of the resulting sequence. Needs to implement . + /// Asynchronous factory function to obtain a resource object. + /// Asynchronous factory function to obtain an observable sequence that depends on the obtained resource. + /// An observable sequence whose lifetime controls the lifetime of the dependent resource object. + /// or is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + /// When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous resource factory and observable factory functions will be signaled. + public static IObservable Using(Func> resourceFactoryAsync, Func>> observableFactoryAsync) where TResource : IDisposable + { + if (resourceFactoryAsync == null) + { + throw new ArgumentNullException(nameof(resourceFactoryAsync)); + } + + if (observableFactoryAsync == null) + { + throw new ArgumentNullException(nameof(observableFactoryAsync)); + } + + return s_impl.Using(resourceFactoryAsync, observableFactoryAsync); + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable.Events.cs b/LibExternal/System.Reactive/Linq/Observable.Events.cs new file mode 100644 index 0000000..502966f --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable.Events.cs @@ -0,0 +1,1523 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics.CodeAnalysis; +using System.Reactive.Concurrency; +using System.Threading; + +namespace System.Reactive.Linq +{ + public static partial class Observable + { + #region + FromEventPattern + + + #region Strongly typed + + #region Action + + /// + /// Converts a .NET event, conforming to the standard .NET event pattern based on , to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEventPattern, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEventPattern, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// + public static IObservable> FromEventPattern(Action addHandler, Action removeHandler) + { + if (addHandler == null) + { + throw new ArgumentNullException(nameof(addHandler)); + } + + if (removeHandler == null) + { + throw new ArgumentNullException(nameof(removeHandler)); + } + + return s_impl.FromEventPattern(addHandler, removeHandler); + } + + /// + /// Converts a .NET event, conforming to the standard .NET event pattern based on , to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// or or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEventPattern that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// + public static IObservable> FromEventPattern(Action addHandler, Action removeHandler, IScheduler scheduler) + { + if (addHandler == null) + { + throw new ArgumentNullException(nameof(addHandler)); + } + + if (removeHandler == null) + { + throw new ArgumentNullException(nameof(removeHandler)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.FromEventPattern(addHandler, removeHandler, scheduler); + } + + #endregion + + #region Action + + /// + /// Converts a .NET event, conforming to the standard .NET event pattern based on a supplied event delegate type, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// The delegate type of the event to be converted. + /// The type of the event data generated by the event. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEventPattern, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEventPattern, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// + public static IObservable> FromEventPattern(Action addHandler, Action removeHandler) + { + if (addHandler == null) + { + throw new ArgumentNullException(nameof(addHandler)); + } + + if (removeHandler == null) + { + throw new ArgumentNullException(nameof(removeHandler)); + } + + return s_impl.FromEventPattern(addHandler, removeHandler); + } + + /// + /// Converts a .NET event, conforming to the standard .NET event pattern based on a supplied event delegate type, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// The delegate type of the event to be converted. + /// The type of the event data generated by the event. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// or or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEventPattern that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// + public static IObservable> FromEventPattern(Action addHandler, Action removeHandler, IScheduler scheduler) + { + if (addHandler == null) + { + throw new ArgumentNullException(nameof(addHandler)); + } + + if (removeHandler == null) + { + throw new ArgumentNullException(nameof(removeHandler)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.FromEventPattern(addHandler, removeHandler, scheduler); + } + + /// + /// Converts a .NET event, conforming to the standard .NET event pattern based on , to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// The delegate type of the event to be converted. + /// The type of the event data generated by the event. + /// A function used to convert the given event handler to a delegate compatible with the underlying .NET event. The resulting delegate is used in calls to the addHandler and removeHandler action parameters. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// or or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEventPattern, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEventPattern, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// + public static IObservable> FromEventPattern(Func, TDelegate> conversion, Action addHandler, Action removeHandler) + { + if (conversion == null) + { + throw new ArgumentNullException(nameof(conversion)); + } + + if (addHandler == null) + { + throw new ArgumentNullException(nameof(addHandler)); + } + + if (removeHandler == null) + { + throw new ArgumentNullException(nameof(removeHandler)); + } + + return s_impl.FromEventPattern(conversion, addHandler, removeHandler); + } + + /// + /// Converts a .NET event, conforming to the standard .NET event pattern based on , to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// The delegate type of the event to be converted. + /// The type of the event data generated by the event. + /// A function used to convert the given event handler to a delegate compatible with the underlying .NET event. The resulting delegate is used in calls to the addHandler and removeHandler action parameters. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// or or or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEventPattern that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// + public static IObservable> FromEventPattern(Func, TDelegate> conversion, Action addHandler, Action removeHandler, IScheduler scheduler) + { + if (conversion == null) + { + throw new ArgumentNullException(nameof(conversion)); + } + + if (addHandler == null) + { + throw new ArgumentNullException(nameof(addHandler)); + } + + if (removeHandler == null) + { + throw new ArgumentNullException(nameof(removeHandler)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.FromEventPattern(conversion, addHandler, removeHandler, scheduler); + } + + /// + /// Converts a .NET event, conforming to the standard .NET event pattern based on a supplied event delegate type with a strongly typed sender parameter, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// The delegate type of the event to be converted. + /// The type of the sender that raises the event. + /// The type of the event data generated by the event. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEventPattern, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEventPattern, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// + public static IObservable> FromEventPattern(Action addHandler, Action removeHandler) + { + if (addHandler == null) + { + throw new ArgumentNullException(nameof(addHandler)); + } + + if (removeHandler == null) + { + throw new ArgumentNullException(nameof(removeHandler)); + } + + return s_impl.FromEventPattern(addHandler, removeHandler); + } + + /// + /// Converts a .NET event, conforming to the standard .NET event pattern based on a supplied event delegate type with a strongly typed sender parameter, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// The delegate type of the event to be converted. + /// The type of the sender that raises the event. + /// The type of the event data generated by the event. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// or or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEventPattern that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// + public static IObservable> FromEventPattern(Action addHandler, Action removeHandler, IScheduler scheduler) + { + if (addHandler == null) + { + throw new ArgumentNullException(nameof(addHandler)); + } + + if (removeHandler == null) + { + throw new ArgumentNullException(nameof(removeHandler)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.FromEventPattern(addHandler, removeHandler, scheduler); + } + + #endregion + + #region Action> + + /// + /// Converts a .NET event, conforming to the standard .NET event pattern based on , to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// The type of the event data generated by the event. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEventPattern, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEventPattern, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// + public static IObservable> FromEventPattern(Action> addHandler, Action> removeHandler) + { + if (addHandler == null) + { + throw new ArgumentNullException(nameof(addHandler)); + } + + if (removeHandler == null) + { + throw new ArgumentNullException(nameof(removeHandler)); + } + + return s_impl.FromEventPattern(addHandler, removeHandler); + } + + /// + /// Converts a .NET event, conforming to the standard .NET event pattern based on , to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// The type of the event data generated by the event. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEventPattern that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// + public static IObservable> FromEventPattern(Action> addHandler, Action> removeHandler, IScheduler scheduler) + { + if (addHandler == null) + { + throw new ArgumentNullException(nameof(addHandler)); + } + + if (removeHandler == null) + { + throw new ArgumentNullException(nameof(removeHandler)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.FromEventPattern(addHandler, removeHandler, scheduler); + } + + #endregion + + #endregion + + #region Reflection + + #region Instance events + + /// + /// Converts an instance .NET event, conforming to the standard .NET event pattern with an parameter, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// Reflection is used to discover the event based on the target object type and the specified event name. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Object instance that exposes the event to convert. + /// Name of the event to convert. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// or is null. + /// The event could not be found. -or- The event does not conform to the standard .NET event pattern. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEventPattern, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEventPattern, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public static IObservable> FromEventPattern(object target, string eventName) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (eventName == null) + { + throw new ArgumentNullException(nameof(eventName)); + } + + return s_impl.FromEventPattern(target, eventName); + } + + /// + /// Converts an instance .NET event, conforming to the standard .NET event pattern with an parameter, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// Reflection is used to discover the event based on the target object type and the specified event name. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Object instance that exposes the event to convert. + /// Name of the event to convert. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// or or is null. + /// The event could not be found. -or- The event does not conform to the standard .NET event pattern. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEventPattern that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public static IObservable> FromEventPattern(object target, string eventName, IScheduler scheduler) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (eventName == null) + { + throw new ArgumentNullException(nameof(eventName)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.FromEventPattern(target, eventName, scheduler); + } + + /// + /// Converts an instance .NET event, conforming to the standard .NET event pattern with strongly typed event arguments, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// Reflection is used to discover the event based on the target object type and the specified event name. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// The type of the event data generated by the event. + /// Object instance that exposes the event to convert. + /// Name of the event to convert. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// or is null. + /// The event could not be found. -or- The event does not conform to the standard .NET event pattern. -or- The event's second argument type is not assignable to TEventArgs. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEventPattern, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEventPattern, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public static IObservable> FromEventPattern(object target, string eventName) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (eventName == null) + { + throw new ArgumentNullException(nameof(eventName)); + } + + return s_impl.FromEventPattern(target, eventName); + } + + /// + /// Converts an instance .NET event, conforming to the standard .NET event pattern with strongly typed event arguments, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// Reflection is used to discover the event based on the target object type and the specified event name. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// The type of the event data generated by the event. + /// Object instance that exposes the event to convert. + /// Name of the event to convert. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// or or is null. + /// The event could not be found. -or- The event does not conform to the standard .NET event pattern. -or- The event's second argument type is not assignable to TEventArgs. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEventPattern that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public static IObservable> FromEventPattern(object target, string eventName, IScheduler scheduler) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (eventName == null) + { + throw new ArgumentNullException(nameof(eventName)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.FromEventPattern(target, eventName, scheduler); + } + + /// + /// Converts an instance .NET event, conforming to the standard .NET event pattern with a strongly typed sender and strongly typed event arguments, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// Reflection is used to discover the event based on the target object type and the specified event name. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// The type of the sender that raises the event. + /// The type of the event data generated by the event. + /// Object instance that exposes the event to convert. + /// Name of the event to convert. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// or is null. + /// The event could not be found. -or- The event does not conform to the standard .NET event pattern. -or- The event's first argument type is not assignable to TSender. -or- The event's second argument type is not assignable to TEventArgs. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEventPattern, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEventPattern, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public static IObservable> FromEventPattern(object target, string eventName) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (eventName == null) + { + throw new ArgumentNullException(nameof(eventName)); + } + + return s_impl.FromEventPattern(target, eventName); + } + + /// + /// Converts an instance .NET event, conforming to the standard .NET event pattern with a strongly typed sender and strongly typed event arguments, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// Reflection is used to discover the event based on the target object type and the specified event name. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// The type of the sender that raises the event. + /// The type of the event data generated by the event. + /// Object instance that exposes the event to convert. + /// Name of the event to convert. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// or or is null. + /// The event could not be found. -or- The event does not conform to the standard .NET event pattern. -or- The event's first argument type is not assignable to TSender. -or- The event's second argument type is not assignable to TEventArgs. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEventPattern that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public static IObservable> FromEventPattern(object target, string eventName, IScheduler scheduler) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (eventName == null) + { + throw new ArgumentNullException(nameof(eventName)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.FromEventPattern(target, eventName, scheduler); + } + + #endregion + + #region Static events + + /// + /// Converts a static .NET event, conforming to the standard .NET event pattern with an parameter, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// Reflection is used to discover the event based on the specified type and the specified event name. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Type that exposes the static event to convert. + /// Name of the event to convert. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// or is null. + /// The event could not be found. -or- The event does not conform to the standard .NET event pattern. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEventPattern, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEventPattern, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public static IObservable> FromEventPattern(Type type, string eventName) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (eventName == null) + { + throw new ArgumentNullException(nameof(eventName)); + } + + return s_impl.FromEventPattern(type, eventName); + } + + /// + /// Converts a static .NET event, conforming to the standard .NET event pattern with an parameter, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// Reflection is used to discover the event based on the specified type and the specified event name. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Type that exposes the static event to convert. + /// Name of the event to convert. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// or or is null. + /// The event could not be found. -or- The event does not conform to the standard .NET event pattern. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEventPattern that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public static IObservable> FromEventPattern(Type type, string eventName, IScheduler scheduler) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (eventName == null) + { + throw new ArgumentNullException(nameof(eventName)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.FromEventPattern(type, eventName, scheduler); + } + + /// + /// Converts a static .NET event, conforming to the standard .NET event pattern with strongly typed event arguments, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// Reflection is used to discover the event based on the specified type and the specified event name. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// The type of the event data generated by the event. + /// Type that exposes the static event to convert. + /// Name of the event to convert. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// or is null. + /// The event could not be found. -or- The event does not conform to the standard .NET event pattern. -or- The event's second argument type is not assignable to TEventArgs. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEventPattern, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEventPattern, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public static IObservable> FromEventPattern(Type type, string eventName) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (eventName == null) + { + throw new ArgumentNullException(nameof(eventName)); + } + + return s_impl.FromEventPattern(type, eventName); + } + + /// + /// Converts a static .NET event, conforming to the standard .NET event pattern with strongly typed event arguments, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// Reflection is used to discover the event based on the specified type and the specified event name. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// The type of the event data generated by the event. + /// Type that exposes the static event to convert. + /// Name of the event to convert. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// or or is null. + /// The event could not be found. -or- The event does not conform to the standard .NET event pattern. -or- The event's second argument type is not assignable to TEventArgs. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEventPattern that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public static IObservable> FromEventPattern(Type type, string eventName, IScheduler scheduler) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (eventName == null) + { + throw new ArgumentNullException(nameof(eventName)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.FromEventPattern(type, eventName, scheduler); + } + + /// + /// Converts a static .NET event, conforming to the standard .NET event pattern with a strongly typed sender and strongly typed event arguments, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// Reflection is used to discover the event based on the specified type and the specified event name. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// The type of the sender that raises the event. + /// The type of the event data generated by the event. + /// Type that exposes the static event to convert. + /// Name of the event to convert. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// or is null. + /// The event could not be found. -or- The event does not conform to the standard .NET event pattern. -or- The event's first argument type is not assignable to TSender. -or- The event's second argument type is not assignable to TEventArgs. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEventPattern, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEventPattern, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public static IObservable> FromEventPattern(Type type, string eventName) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (eventName == null) + { + throw new ArgumentNullException(nameof(eventName)); + } + + return s_impl.FromEventPattern(type, eventName); + } + + /// + /// Converts a static .NET event, conforming to the standard .NET event pattern with a strongly typed sender and strongly typed event arguments, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// Reflection is used to discover the event based on the specified type and the specified event name. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// The type of the sender that raises the event. + /// The type of the event data generated by the event. + /// Type that exposes the static event to convert. + /// Name of the event to convert. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// or or is null. + /// The event could not be found. -or- The event does not conform to the standard .NET event pattern. -or- The event's first argument type is not assignable to TSender. -or- The event's second argument type is not assignable to TEventArgs. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEventPattern that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public static IObservable> FromEventPattern(Type type, string eventName, IScheduler scheduler) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (eventName == null) + { + throw new ArgumentNullException(nameof(eventName)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.FromEventPattern(type, eventName, scheduler); + } + + #endregion + + #endregion + + #endregion + + #region + FromEvent + + + #region Action + + /// + /// Converts a .NET event to an observable sequence, using a conversion function to obtain the event delegate. Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events conforming to the standard .NET event pattern, use any of the FromEventPattern overloads instead. + /// + /// The delegate type of the event to be converted. + /// The type of the event data generated by the event. + /// A function used to convert the given event handler to a delegate compatible with the underlying .NET event. The resulting delegate is used in calls to the addHandler and removeHandler action parameters. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The observable sequence that contains the event argument objects passed to the invocations of the underlying .NET event. + /// or or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEvent, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEvent, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEvent calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// + public static IObservable FromEvent(Func, TDelegate> conversion, Action addHandler, Action removeHandler) + { + if (conversion == null) + { + throw new ArgumentNullException(nameof(conversion)); + } + + if (addHandler == null) + { + throw new ArgumentNullException(nameof(addHandler)); + } + + if (removeHandler == null) + { + throw new ArgumentNullException(nameof(removeHandler)); + } + + return s_impl.FromEvent(conversion, addHandler, removeHandler); + } + + /// + /// Converts a .NET event to an observable sequence, using a conversion function to obtain the event delegate. Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events conforming to the standard .NET event pattern, use any of the FromEventPattern overloads instead. + /// + /// The delegate type of the event to be converted. + /// The type of the event data generated by the event. + /// A function used to convert the given event handler to a delegate compatible with the underlying .NET event. The resulting delegate is used in calls to the addHandler and removeHandler action parameters. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains the event argument objects passed to the invocations of the underlying .NET event. + /// or or or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEvent calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEvent that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// + public static IObservable FromEvent(Func, TDelegate> conversion, Action addHandler, Action removeHandler, IScheduler scheduler) + { + if (conversion == null) + { + throw new ArgumentNullException(nameof(conversion)); + } + + if (addHandler == null) + { + throw new ArgumentNullException(nameof(addHandler)); + } + + if (removeHandler == null) + { + throw new ArgumentNullException(nameof(removeHandler)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.FromEvent(conversion, addHandler, removeHandler, scheduler); + } + + /// + /// Converts a .NET event to an observable sequence, using a supplied event delegate type. Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events conforming to the standard .NET event pattern, use any of the FromEventPattern overloads instead. + /// + /// The delegate type of the event to be converted. + /// The type of the event data generated by the event. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The observable sequence that contains the event argument objects passed to the invocations of the underlying .NET event. + /// or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEvent, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEvent, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEvent calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// + public static IObservable FromEvent(Action addHandler, Action removeHandler) + { + if (addHandler == null) + { + throw new ArgumentNullException(nameof(addHandler)); + } + + if (removeHandler == null) + { + throw new ArgumentNullException(nameof(removeHandler)); + } + + return s_impl.FromEvent(addHandler, removeHandler); + } + + /// + /// Converts a .NET event to an observable sequence, using a supplied event delegate type. Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events conforming to the standard .NET event pattern, use any of the FromEventPattern overloads instead. + /// + /// The delegate type of the event to be converted. + /// The type of the event data generated by the event. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains the event argument objects passed to the invocations of the underlying .NET event. + /// or or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEvent calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEvent that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// + public static IObservable FromEvent(Action addHandler, Action removeHandler, IScheduler scheduler) + { + if (addHandler == null) + { + throw new ArgumentNullException(nameof(addHandler)); + } + + if (removeHandler == null) + { + throw new ArgumentNullException(nameof(removeHandler)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.FromEvent(addHandler, removeHandler, scheduler); + } + + #endregion + + #region Action> + + /// + /// Converts a generic Action-based .NET event to an observable sequence. Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events conforming to the standard .NET event pattern, use any of the FromEventPattern overloads instead. + /// + /// The type of the event data generated by the event. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The observable sequence that contains the event argument objects passed to the invocations of the underlying .NET event. + /// or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEvent, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEvent, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEvent calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// + public static IObservable FromEvent(Action> addHandler, Action> removeHandler) + { + if (addHandler == null) + { + throw new ArgumentNullException(nameof(addHandler)); + } + + if (removeHandler == null) + { + throw new ArgumentNullException(nameof(removeHandler)); + } + + return s_impl.FromEvent(addHandler, removeHandler); + } + + /// + /// Converts a generic Action-based .NET event to an observable sequence. Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events conforming to the standard .NET event pattern, use any of the FromEventPattern overloads instead. + /// + /// The type of the event data generated by the event. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains the event argument objects passed to the invocations of the underlying .NET event. + /// or or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEvent calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEvent that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// + public static IObservable FromEvent(Action> addHandler, Action> removeHandler, IScheduler scheduler) + { + if (addHandler == null) + { + throw new ArgumentNullException(nameof(addHandler)); + } + + if (removeHandler == null) + { + throw new ArgumentNullException(nameof(removeHandler)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.FromEvent(addHandler, removeHandler, scheduler); + } + + #endregion + + #region Action + + /// + /// Converts an Action-based .NET event to an observable sequence. Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events conforming to the standard .NET event pattern, use any of the FromEventPattern overloads instead. + /// + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The observable sequence that contains the event argument objects passed to the invocations of the underlying .NET event. + /// or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEvent, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEvent, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEvent calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// + public static IObservable FromEvent(Action addHandler, Action removeHandler) + { + if (addHandler == null) + { + throw new ArgumentNullException(nameof(addHandler)); + } + + if (removeHandler == null) + { + throw new ArgumentNullException(nameof(removeHandler)); + } + + return s_impl.FromEvent(addHandler, removeHandler); + } + + /// + /// Converts an Action-based .NET event to an observable sequence. Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events conforming to the standard .NET event pattern, use any of the FromEventPattern overloads instead. + /// + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains the event argument objects passed to the invocations of the underlying .NET event. + /// or or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEvent calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEvent that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// + public static IObservable FromEvent(Action addHandler, Action removeHandler, IScheduler scheduler) + { + if (addHandler == null) + { + throw new ArgumentNullException(nameof(addHandler)); + } + + if (removeHandler == null) + { + throw new ArgumentNullException(nameof(removeHandler)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.FromEvent(addHandler, removeHandler, scheduler); + } + + #endregion + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable.Imperative.cs b/LibExternal/System.Reactive/Linq/Observable.Imperative.cs new file mode 100644 index 0000000..dafacb2 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable.Imperative.cs @@ -0,0 +1,376 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Concurrency; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Reactive.Linq +{ + public static partial class Observable + { + #region + ForEachAsync + + + /// + /// Invokes an action for each element in the observable sequence, and returns a Task object that will get signaled when the sequence terminates. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Action to invoke for each element in the observable sequence. + /// Task that signals the termination of the sequence. + /// or is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + public static Task ForEachAsync(this IObservable source, Action onNext) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + return s_impl.ForEachAsync(source, onNext); + } + + /// + /// Invokes an action for each element in the observable sequence, and returns a Task object that will get signaled when the sequence terminates. + /// The loop can be quit prematurely by setting the specified cancellation token. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Action to invoke for each element in the observable sequence. + /// Cancellation token used to stop the loop. + /// Task that signals the termination of the sequence. + /// or is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + public static Task ForEachAsync(this IObservable source, Action onNext, CancellationToken cancellationToken) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + return s_impl.ForEachAsync(source, onNext, cancellationToken); + } + + /// + /// Invokes an action for each element in the observable sequence, incorporating the element's index, and returns a Task object that will get signaled when the sequence terminates. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Action to invoke for each element in the observable sequence. + /// Task that signals the termination of the sequence. + /// or is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + public static Task ForEachAsync(this IObservable source, Action onNext) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + return s_impl.ForEachAsync(source, onNext); + } + + /// + /// Invokes an action for each element in the observable sequence, incorporating the element's index, and returns a Task object that will get signaled when the sequence terminates. + /// The loop can be quit prematurely by setting the specified cancellation token. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Action to invoke for each element in the observable sequence. + /// Cancellation token used to stop the loop. + /// Task that signals the termination of the sequence. + /// or is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + public static Task ForEachAsync(this IObservable source, Action onNext, CancellationToken cancellationToken) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + return s_impl.ForEachAsync(source, onNext, cancellationToken); + } + + #endregion + + #region + Case + + + /// + /// Uses to determine which source in to return, choosing if no match is found. + /// + /// The type of the value returned by the selector function, used to look up the resulting source. + /// The type of the elements in the result sequence. + /// Selector function invoked to determine the source to lookup in the dictionary. + /// Dictionary of sources to select from based on the invocation result. + /// Default source to select in case no matching source in is found. + /// The observable sequence retrieved from the dictionary based on the invocation result, or if no match is found. + /// or or is null. + public static IObservable Case(Func selector, IDictionary> sources, IObservable defaultSource) + where TValue : notnull + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + if (defaultSource == null) + { + throw new ArgumentNullException(nameof(defaultSource)); + } + + return s_impl.Case(selector, sources, defaultSource); + } + + /// + /// Uses to determine which source in to return, choosing an empty sequence on the specified scheduler if no match is found. + /// + /// The type of the value returned by the selector function, used to look up the resulting source. + /// The type of the elements in the result sequence. + /// Selector function invoked to determine the source to lookup in the dictionary. + /// Dictionary of sources to select from based on the invocation result. + /// Scheduler to generate an empty sequence on in case no matching source in is found. + /// The observable sequence retrieved from the dictionary based on the invocation result, or an empty sequence if no match is found. + /// or or is null. + public static IObservable Case(Func selector, IDictionary> sources, IScheduler scheduler) + where TValue : notnull + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Case(selector, sources, scheduler); + } + + /// + /// Uses to determine which source in to return, choosing an empty sequence if no match is found. + /// + /// The type of the value returned by the selector function, used to look up the resulting source. + /// The type of the elements in the result sequence. + /// Selector function invoked to determine the source to lookup in the dictionary. + /// Dictionary of sources to select from based on the invocation result. + /// The observable sequence retrieved from the dictionary based on the invocation result, or an empty sequence if no match is found. + /// or is null. + public static IObservable Case(Func selector, IDictionary> sources) + where TValue : notnull + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + return s_impl.Case(selector, sources); + } + + #endregion + + #region + DoWhile + + + /// + /// Repeats the given as long as the specified holds, where the is evaluated after each repeated completed. + /// + /// The type of the elements in the source sequence. + /// Source to repeat as long as the function evaluates to true. + /// Condition that will be evaluated upon the completion of an iteration through the , to determine whether repetition of the source is required. + /// The observable sequence obtained by concatenating the sequence as long as the holds. + /// or is null. + public static IObservable DoWhile(this IObservable source, Func condition) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (condition == null) + { + throw new ArgumentNullException(nameof(condition)); + } + + return s_impl.DoWhile(source, condition); + } + + #endregion + + #region + For + + + /// + /// Concatenates the observable sequences obtained by running the for each element in the given enumerable . + /// + /// The type of the elements in the enumerable source sequence. + /// The type of the elements in the observable result sequence. + /// Enumerable source for which each element will be mapped onto an observable source that will be concatenated in the result sequence. + /// Function to select an observable source for each element in the . + /// The observable sequence obtained by concatenating the sources returned by for each element in the . + /// or is null. + public static IObservable For(IEnumerable source, Func> resultSelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.For(source, resultSelector); + } + + #endregion + + #region + If + + + /// + /// If the specified evaluates true, select the sequence. Otherwise, select the sequence. + /// + /// The type of the elements in the result sequence. + /// Condition evaluated to decide which sequence to return. + /// Sequence returned in case evaluates true. + /// Sequence returned in case evaluates false. + /// if evaluates true; otherwise. + /// or or is null. + public static IObservable If(Func condition, IObservable thenSource, IObservable elseSource) + { + if (condition == null) + { + throw new ArgumentNullException(nameof(condition)); + } + + if (thenSource == null) + { + throw new ArgumentNullException(nameof(thenSource)); + } + + if (elseSource == null) + { + throw new ArgumentNullException(nameof(elseSource)); + } + + return s_impl.If(condition, thenSource, elseSource); + } + + /// + /// If the specified evaluates true, select the sequence. Otherwise, return an empty sequence. + /// + /// The type of the elements in the result sequence. + /// Condition evaluated to decide which sequence to return. + /// Sequence returned in case evaluates true. + /// if evaluates true; an empty sequence otherwise. + /// or is null. + public static IObservable If(Func condition, IObservable thenSource) + { + if (condition == null) + { + throw new ArgumentNullException(nameof(condition)); + } + + if (thenSource == null) + { + throw new ArgumentNullException(nameof(thenSource)); + } + + return s_impl.If(condition, thenSource); + } + + /// + /// If the specified evaluates true, select the sequence. Otherwise, return an empty sequence generated on the specified scheduler. + /// + /// The type of the elements in the result sequence. + /// Condition evaluated to decide which sequence to return. + /// Sequence returned in case evaluates true. + /// Scheduler to generate an empty sequence on in case evaluates false. + /// if evaluates true; an empty sequence otherwise. + /// or or is null. + public static IObservable If(Func condition, IObservable thenSource, IScheduler scheduler) + { + if (condition == null) + { + throw new ArgumentNullException(nameof(condition)); + } + + if (thenSource == null) + { + throw new ArgumentNullException(nameof(thenSource)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.If(condition, thenSource, scheduler); + } + + #endregion + + #region + While + + + /// + /// Repeats the given as long as the specified holds, where the is evaluated before each repeated is subscribed to. + /// + /// The type of the elements in the source sequence. + /// Source to repeat as long as the function evaluates to true. + /// Condition that will be evaluated before subscription to the , to determine whether repetition of the source is required. + /// The observable sequence obtained by concatenating the sequence as long as the holds. + /// or is null. + public static IObservable While(Func condition, IObservable source) + { + if (condition == null) + { + throw new ArgumentNullException(nameof(condition)); + } + + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.While(condition, source); + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable.Joins.cs b/LibExternal/System.Reactive/Linq/Observable.Joins.cs new file mode 100644 index 0000000..1039927 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable.Joins.cs @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Joins; + +namespace System.Reactive.Linq +{ + public static partial class Observable + { + #region And + + /// + /// Creates a pattern that matches when both observable sequences have an available element. + /// + /// The type of the elements in the left sequence. + /// The type of the elements in the right sequence. + /// Observable sequence to match with the right sequence. + /// Observable sequence to match with the left sequence. + /// Pattern object that matches when both observable sequences have an available element. + /// or is null. + public static Pattern And(this IObservable left, IObservable right) + { + if (left == null) + { + throw new ArgumentNullException(nameof(left)); + } + + if (right == null) + { + throw new ArgumentNullException(nameof(right)); + } + + return s_impl.And(left, right); + } + + #endregion + + #region Then + + /// + /// Matches when the observable sequence has an available element and projects the element by invoking the selector function. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// Observable sequence to apply the selector on. + /// Selector that will be invoked for elements in the source sequence. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// or is null. + public static Plan Then(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Then(source, selector); + } + + #endregion + + #region When + + /// + /// Joins together the results from several patterns. + /// + /// The type of the elements in the result sequence, obtained from the specified patterns. + /// A series of plans created by use of the Then operator on patterns. + /// An observable sequence with the results from matching several patterns. + /// is null. + public static IObservable When(params Plan[] plans) + { + if (plans == null) + { + throw new ArgumentNullException(nameof(plans)); + } + + return s_impl.When(plans); + } + + /// + /// Joins together the results from several patterns. + /// + /// The type of the elements in the result sequence, obtained from the specified patterns. + /// A series of plans created by use of the Then operator on patterns. + /// An observable sequence with the results form matching several patterns. + /// is null. + public static IObservable When(this IEnumerable> plans) + { + if (plans == null) + { + throw new ArgumentNullException(nameof(plans)); + } + + return s_impl.When(plans); + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable.Multiple.CombineLatest.cs b/LibExternal/System.Reactive/Linq/Observable.Multiple.CombineLatest.cs new file mode 100644 index 0000000..e1ae7a0 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable.Multiple.CombineLatest.cs @@ -0,0 +1,1418 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +// This code was generated by a T4 template at 10/05/2020 14:49:21. + +namespace System.Reactive.Linq +{ + public static partial class Observable + { + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IObservable CombineLatest(this IObservable source1, IObservable source2, IObservable source3, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.CombineLatest(source1, source2, source3, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IObservable CombineLatest(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.CombineLatest(source1, source2, source3, source4, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IObservable CombineLatest(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (source5 == null) + { + throw new ArgumentNullException(nameof(source5)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.CombineLatest(source1, source2, source3, source4, source5, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IObservable CombineLatest(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (source5 == null) + { + throw new ArgumentNullException(nameof(source5)); + } + + if (source6 == null) + { + throw new ArgumentNullException(nameof(source6)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.CombineLatest(source1, source2, source3, source4, source5, source6, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IObservable CombineLatest(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (source5 == null) + { + throw new ArgumentNullException(nameof(source5)); + } + + if (source6 == null) + { + throw new ArgumentNullException(nameof(source6)); + } + + if (source7 == null) + { + throw new ArgumentNullException(nameof(source7)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.CombineLatest(source1, source2, source3, source4, source5, source6, source7, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IObservable CombineLatest(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (source5 == null) + { + throw new ArgumentNullException(nameof(source5)); + } + + if (source6 == null) + { + throw new ArgumentNullException(nameof(source6)); + } + + if (source7 == null) + { + throw new ArgumentNullException(nameof(source7)); + } + + if (source8 == null) + { + throw new ArgumentNullException(nameof(source8)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.CombineLatest(source1, source2, source3, source4, source5, source6, source7, source8, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or or or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IObservable CombineLatest(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (source5 == null) + { + throw new ArgumentNullException(nameof(source5)); + } + + if (source6 == null) + { + throw new ArgumentNullException(nameof(source6)); + } + + if (source7 == null) + { + throw new ArgumentNullException(nameof(source7)); + } + + if (source8 == null) + { + throw new ArgumentNullException(nameof(source8)); + } + + if (source9 == null) + { + throw new ArgumentNullException(nameof(source9)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.CombineLatest(source1, source2, source3, source4, source5, source6, source7, source8, source9, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or or or or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IObservable CombineLatest(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (source5 == null) + { + throw new ArgumentNullException(nameof(source5)); + } + + if (source6 == null) + { + throw new ArgumentNullException(nameof(source6)); + } + + if (source7 == null) + { + throw new ArgumentNullException(nameof(source7)); + } + + if (source8 == null) + { + throw new ArgumentNullException(nameof(source8)); + } + + if (source9 == null) + { + throw new ArgumentNullException(nameof(source9)); + } + + if (source10 == null) + { + throw new ArgumentNullException(nameof(source10)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.CombineLatest(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or or or or or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IObservable CombineLatest(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (source5 == null) + { + throw new ArgumentNullException(nameof(source5)); + } + + if (source6 == null) + { + throw new ArgumentNullException(nameof(source6)); + } + + if (source7 == null) + { + throw new ArgumentNullException(nameof(source7)); + } + + if (source8 == null) + { + throw new ArgumentNullException(nameof(source8)); + } + + if (source9 == null) + { + throw new ArgumentNullException(nameof(source9)); + } + + if (source10 == null) + { + throw new ArgumentNullException(nameof(source10)); + } + + if (source11 == null) + { + throw new ArgumentNullException(nameof(source11)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.CombineLatest(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, source11, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or or or or or or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IObservable CombineLatest(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (source5 == null) + { + throw new ArgumentNullException(nameof(source5)); + } + + if (source6 == null) + { + throw new ArgumentNullException(nameof(source6)); + } + + if (source7 == null) + { + throw new ArgumentNullException(nameof(source7)); + } + + if (source8 == null) + { + throw new ArgumentNullException(nameof(source8)); + } + + if (source9 == null) + { + throw new ArgumentNullException(nameof(source9)); + } + + if (source10 == null) + { + throw new ArgumentNullException(nameof(source10)); + } + + if (source11 == null) + { + throw new ArgumentNullException(nameof(source11)); + } + + if (source12 == null) + { + throw new ArgumentNullException(nameof(source12)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.CombineLatest(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, source11, source12, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Thirteenth observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or or or or or or or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IObservable CombineLatest(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (source5 == null) + { + throw new ArgumentNullException(nameof(source5)); + } + + if (source6 == null) + { + throw new ArgumentNullException(nameof(source6)); + } + + if (source7 == null) + { + throw new ArgumentNullException(nameof(source7)); + } + + if (source8 == null) + { + throw new ArgumentNullException(nameof(source8)); + } + + if (source9 == null) + { + throw new ArgumentNullException(nameof(source9)); + } + + if (source10 == null) + { + throw new ArgumentNullException(nameof(source10)); + } + + if (source11 == null) + { + throw new ArgumentNullException(nameof(source11)); + } + + if (source12 == null) + { + throw new ArgumentNullException(nameof(source12)); + } + + if (source13 == null) + { + throw new ArgumentNullException(nameof(source13)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.CombineLatest(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, source11, source12, source13, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the fourteenth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Thirteenth observable source. + /// Fourteenth observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or or or or or or or or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IObservable CombineLatest(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (source5 == null) + { + throw new ArgumentNullException(nameof(source5)); + } + + if (source6 == null) + { + throw new ArgumentNullException(nameof(source6)); + } + + if (source7 == null) + { + throw new ArgumentNullException(nameof(source7)); + } + + if (source8 == null) + { + throw new ArgumentNullException(nameof(source8)); + } + + if (source9 == null) + { + throw new ArgumentNullException(nameof(source9)); + } + + if (source10 == null) + { + throw new ArgumentNullException(nameof(source10)); + } + + if (source11 == null) + { + throw new ArgumentNullException(nameof(source11)); + } + + if (source12 == null) + { + throw new ArgumentNullException(nameof(source12)); + } + + if (source13 == null) + { + throw new ArgumentNullException(nameof(source13)); + } + + if (source14 == null) + { + throw new ArgumentNullException(nameof(source14)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.CombineLatest(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, source11, source12, source13, source14, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the fourteenth source sequence. + /// The type of the elements in the fifteenth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Thirteenth observable source. + /// Fourteenth observable source. + /// Fifteenth observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or or or or or or or or or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IObservable CombineLatest(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, IObservable source15, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (source5 == null) + { + throw new ArgumentNullException(nameof(source5)); + } + + if (source6 == null) + { + throw new ArgumentNullException(nameof(source6)); + } + + if (source7 == null) + { + throw new ArgumentNullException(nameof(source7)); + } + + if (source8 == null) + { + throw new ArgumentNullException(nameof(source8)); + } + + if (source9 == null) + { + throw new ArgumentNullException(nameof(source9)); + } + + if (source10 == null) + { + throw new ArgumentNullException(nameof(source10)); + } + + if (source11 == null) + { + throw new ArgumentNullException(nameof(source11)); + } + + if (source12 == null) + { + throw new ArgumentNullException(nameof(source12)); + } + + if (source13 == null) + { + throw new ArgumentNullException(nameof(source13)); + } + + if (source14 == null) + { + throw new ArgumentNullException(nameof(source14)); + } + + if (source15 == null) + { + throw new ArgumentNullException(nameof(source15)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.CombineLatest(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, source11, source12, source13, source14, source15, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the fourteenth source sequence. + /// The type of the elements in the fifteenth source sequence. + /// The type of the elements in the sixteenth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Thirteenth observable source. + /// Fourteenth observable source. + /// Fifteenth observable source. + /// Sixteenth observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or or or or or or or or or or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IObservable CombineLatest(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, IObservable source15, IObservable source16, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (source5 == null) + { + throw new ArgumentNullException(nameof(source5)); + } + + if (source6 == null) + { + throw new ArgumentNullException(nameof(source6)); + } + + if (source7 == null) + { + throw new ArgumentNullException(nameof(source7)); + } + + if (source8 == null) + { + throw new ArgumentNullException(nameof(source8)); + } + + if (source9 == null) + { + throw new ArgumentNullException(nameof(source9)); + } + + if (source10 == null) + { + throw new ArgumentNullException(nameof(source10)); + } + + if (source11 == null) + { + throw new ArgumentNullException(nameof(source11)); + } + + if (source12 == null) + { + throw new ArgumentNullException(nameof(source12)); + } + + if (source13 == null) + { + throw new ArgumentNullException(nameof(source13)); + } + + if (source14 == null) + { + throw new ArgumentNullException(nameof(source14)); + } + + if (source15 == null) + { + throw new ArgumentNullException(nameof(source15)); + } + + if (source16 == null) + { + throw new ArgumentNullException(nameof(source16)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.CombineLatest(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, source11, source12, source13, source14, source15, source16, resultSelector); + } + + } + +#pragma warning disable CA1711 // (Don't use Ex suffix.) This has been a public type for many years, so we can't rename it now. + public static partial class ObservableEx +#pragma warning restore CA1711 + { + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// First observable source. + /// Second observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or is null. + public static IObservable<(TFirst First, TSecond Second)> CombineLatest(this IObservable first, IObservable second) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + + return s_impl.CombineLatest(first, second); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or is null. + public static IObservable<(TFirst First, TSecond Second, TThird Third)> CombineLatest(this IObservable first, IObservable second, IObservable third) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + + return s_impl.CombineLatest(first, second, third); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or is null. + public static IObservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth)> CombineLatest(this IObservable first, IObservable second, IObservable third, IObservable fourth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + + return s_impl.CombineLatest(first, second, third, fourth); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or is null. + public static IObservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth)> CombineLatest(this IObservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + + return s_impl.CombineLatest(first, second, third, fourth, fifth); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or is null. + public static IObservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth)> CombineLatest(this IObservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + + return s_impl.CombineLatest(first, second, third, fourth, fifth, sixth); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or or is null. + public static IObservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh)> CombineLatest(this IObservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + if (seventh == null) + throw new ArgumentNullException(nameof(seventh)); + + return s_impl.CombineLatest(first, second, third, fourth, fifth, sixth, seventh); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or or or is null. + public static IObservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth)> CombineLatest(this IObservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + if (seventh == null) + throw new ArgumentNullException(nameof(seventh)); + if (eighth == null) + throw new ArgumentNullException(nameof(eighth)); + + return s_impl.CombineLatest(first, second, third, fourth, fifth, sixth, seventh, eighth); + } + + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable.Multiple.CombineLatest.tt b/LibExternal/System.Reactive/Linq/Observable.Multiple.CombineLatest.tt new file mode 100644 index 0000000..3a175af --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable.Multiple.CombineLatest.tt @@ -0,0 +1,134 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> +// This code was generated by a T4 template at <#=DateTime.Now#>. + +namespace System.Reactive.Linq +{ + public static partial class Observable + { +<# +Func toUpper = s => char.ToUpper(s[0]) + s.Substring(1); + +string[] ordinals = new[] { "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth", "tenth", "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", "sixteenth" }; + +for (int i = 3; i <= 16; i++) +{ + var parameters = string.Join(", ", Enumerable.Range(1, i).Select(j => "IObservable source" + j)); + var genArgs = string.Join(", ", Enumerable.Range(1, i).Select(j => "TSource" + j)); + var sources = string.Join(", ", Enumerable.Range(1, i).Select(j => "source" + j)); + var paramRefs = string.Join(" or ", Enumerable.Range(1, i).Select(j => "")); + +#> + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// +<# +for (int j = 0; j < i; j++) +{ +#> + /// The type of the elements in the <#=ordinals[j]#> source sequence. +<# +} +#> + /// The type of the elements in the result sequence, returned by the selector function. +<# +for (int j = 0; j < i; j++) +{ +#> + /// <#=toUpper(ordinals[j])#> observable source. +<# +} +#> + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// <#=paramRefs#> or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IObservable CombineLatest<<#=genArgs#>, TResult>(this <#=parameters#>, Func<<#=genArgs#>, TResult> resultSelector) + { +<# +for (int j = 0; j < i; j++) +{ +#> + if (source<#=j + 1#> == null) + { + throw new ArgumentNullException(nameof(source<#=j + 1#>)); + } + +<# +} +#> + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.CombineLatest(<#=sources#>, resultSelector); + } + +<# +} +#> + } + + public static partial class ObservableEx + { +<# +for (int i = 2; i <= 8; i++) +{ + var parameters = string.Join(", ", Enumerable.Range(1, i).Select(j => "IObservable " + ordinals[j - 1])); + var genArgs = string.Join(", ", Enumerable.Range(1, i).Select(j => "T" + toUpper(ordinals[j - 1]))); + var sources = string.Join(", ", Enumerable.Range(1, i).Select(j => ordinals[j - 1])); + var paramRefs = string.Join(" or ", Enumerable.Range(1, i).Select(j => "")); + var tuple = "(" + string.Join(", ", Enumerable.Range(1, i).Select(j => "T" + toUpper(ordinals[j - 1]) + " " + toUpper(ordinals[j - 1]))) + ")"; + var vals = string.Join(", ", Enumerable.Range(1, i).Select(j => "t" + j)); + +#> + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever any of the observable sequences produces an element. + /// +<# +for (int j = 0; j < i; j++) +{ +#> + /// The type of the elements in the <#=ordinals[j]#> source sequence. +<# +} +#> +<# +for (int j = 0; j < i; j++) +{ +#> + /// <#=toUpper(ordinals[j])#> observable source. +<# +} +#> + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// <#=paramRefs#> is null. + public static IObservable<<#=tuple#>> CombineLatest<<#=genArgs#>>(this <#=parameters#>) + { +<# +for (int j = 0; j < i; j++) +{ +#> + if (<#=ordinals[j]#> == null) + throw new ArgumentNullException(nameof(<#=ordinals[j]#>)); +<# +} +#> + + return s_impl.CombineLatest(<#=sources#>); + } + +<# +} +#> + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable.Multiple.Zip.cs b/LibExternal/System.Reactive/Linq/Observable.Multiple.Zip.cs new file mode 100644 index 0000000..046d00e --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable.Multiple.Zip.cs @@ -0,0 +1,1404 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +// This code was generated by a T4 template at 10/05/2020 14:49:15. + +namespace System.Reactive.Linq +{ + public static partial class Observable + { + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or is null. + public static IObservable Zip(this IObservable source1, IObservable source2, IObservable source3, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.Zip(source1, source2, source3, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or is null. + public static IObservable Zip(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.Zip(source1, source2, source3, source4, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or or is null. + public static IObservable Zip(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (source5 == null) + { + throw new ArgumentNullException(nameof(source5)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.Zip(source1, source2, source3, source4, source5, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or or or is null. + public static IObservable Zip(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (source5 == null) + { + throw new ArgumentNullException(nameof(source5)); + } + + if (source6 == null) + { + throw new ArgumentNullException(nameof(source6)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.Zip(source1, source2, source3, source4, source5, source6, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or or or or is null. + public static IObservable Zip(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (source5 == null) + { + throw new ArgumentNullException(nameof(source5)); + } + + if (source6 == null) + { + throw new ArgumentNullException(nameof(source6)); + } + + if (source7 == null) + { + throw new ArgumentNullException(nameof(source7)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.Zip(source1, source2, source3, source4, source5, source6, source7, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or or or or or is null. + public static IObservable Zip(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (source5 == null) + { + throw new ArgumentNullException(nameof(source5)); + } + + if (source6 == null) + { + throw new ArgumentNullException(nameof(source6)); + } + + if (source7 == null) + { + throw new ArgumentNullException(nameof(source7)); + } + + if (source8 == null) + { + throw new ArgumentNullException(nameof(source8)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.Zip(source1, source2, source3, source4, source5, source6, source7, source8, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or or or or or or is null. + public static IObservable Zip(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (source5 == null) + { + throw new ArgumentNullException(nameof(source5)); + } + + if (source6 == null) + { + throw new ArgumentNullException(nameof(source6)); + } + + if (source7 == null) + { + throw new ArgumentNullException(nameof(source7)); + } + + if (source8 == null) + { + throw new ArgumentNullException(nameof(source8)); + } + + if (source9 == null) + { + throw new ArgumentNullException(nameof(source9)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.Zip(source1, source2, source3, source4, source5, source6, source7, source8, source9, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or or or or or or or is null. + public static IObservable Zip(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (source5 == null) + { + throw new ArgumentNullException(nameof(source5)); + } + + if (source6 == null) + { + throw new ArgumentNullException(nameof(source6)); + } + + if (source7 == null) + { + throw new ArgumentNullException(nameof(source7)); + } + + if (source8 == null) + { + throw new ArgumentNullException(nameof(source8)); + } + + if (source9 == null) + { + throw new ArgumentNullException(nameof(source9)); + } + + if (source10 == null) + { + throw new ArgumentNullException(nameof(source10)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.Zip(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or or or or or or or or is null. + public static IObservable Zip(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (source5 == null) + { + throw new ArgumentNullException(nameof(source5)); + } + + if (source6 == null) + { + throw new ArgumentNullException(nameof(source6)); + } + + if (source7 == null) + { + throw new ArgumentNullException(nameof(source7)); + } + + if (source8 == null) + { + throw new ArgumentNullException(nameof(source8)); + } + + if (source9 == null) + { + throw new ArgumentNullException(nameof(source9)); + } + + if (source10 == null) + { + throw new ArgumentNullException(nameof(source10)); + } + + if (source11 == null) + { + throw new ArgumentNullException(nameof(source11)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.Zip(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, source11, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or or or or or or or or or is null. + public static IObservable Zip(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (source5 == null) + { + throw new ArgumentNullException(nameof(source5)); + } + + if (source6 == null) + { + throw new ArgumentNullException(nameof(source6)); + } + + if (source7 == null) + { + throw new ArgumentNullException(nameof(source7)); + } + + if (source8 == null) + { + throw new ArgumentNullException(nameof(source8)); + } + + if (source9 == null) + { + throw new ArgumentNullException(nameof(source9)); + } + + if (source10 == null) + { + throw new ArgumentNullException(nameof(source10)); + } + + if (source11 == null) + { + throw new ArgumentNullException(nameof(source11)); + } + + if (source12 == null) + { + throw new ArgumentNullException(nameof(source12)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.Zip(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, source11, source12, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Thirteenth observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or or or or or or or or or or is null. + public static IObservable Zip(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (source5 == null) + { + throw new ArgumentNullException(nameof(source5)); + } + + if (source6 == null) + { + throw new ArgumentNullException(nameof(source6)); + } + + if (source7 == null) + { + throw new ArgumentNullException(nameof(source7)); + } + + if (source8 == null) + { + throw new ArgumentNullException(nameof(source8)); + } + + if (source9 == null) + { + throw new ArgumentNullException(nameof(source9)); + } + + if (source10 == null) + { + throw new ArgumentNullException(nameof(source10)); + } + + if (source11 == null) + { + throw new ArgumentNullException(nameof(source11)); + } + + if (source12 == null) + { + throw new ArgumentNullException(nameof(source12)); + } + + if (source13 == null) + { + throw new ArgumentNullException(nameof(source13)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.Zip(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, source11, source12, source13, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the fourteenth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Thirteenth observable source. + /// Fourteenth observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or or or or or or or or or or or is null. + public static IObservable Zip(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (source5 == null) + { + throw new ArgumentNullException(nameof(source5)); + } + + if (source6 == null) + { + throw new ArgumentNullException(nameof(source6)); + } + + if (source7 == null) + { + throw new ArgumentNullException(nameof(source7)); + } + + if (source8 == null) + { + throw new ArgumentNullException(nameof(source8)); + } + + if (source9 == null) + { + throw new ArgumentNullException(nameof(source9)); + } + + if (source10 == null) + { + throw new ArgumentNullException(nameof(source10)); + } + + if (source11 == null) + { + throw new ArgumentNullException(nameof(source11)); + } + + if (source12 == null) + { + throw new ArgumentNullException(nameof(source12)); + } + + if (source13 == null) + { + throw new ArgumentNullException(nameof(source13)); + } + + if (source14 == null) + { + throw new ArgumentNullException(nameof(source14)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.Zip(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, source11, source12, source13, source14, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the fourteenth source sequence. + /// The type of the elements in the fifteenth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Thirteenth observable source. + /// Fourteenth observable source. + /// Fifteenth observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or or or or or or or or or or or or is null. + public static IObservable Zip(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, IObservable source15, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (source5 == null) + { + throw new ArgumentNullException(nameof(source5)); + } + + if (source6 == null) + { + throw new ArgumentNullException(nameof(source6)); + } + + if (source7 == null) + { + throw new ArgumentNullException(nameof(source7)); + } + + if (source8 == null) + { + throw new ArgumentNullException(nameof(source8)); + } + + if (source9 == null) + { + throw new ArgumentNullException(nameof(source9)); + } + + if (source10 == null) + { + throw new ArgumentNullException(nameof(source10)); + } + + if (source11 == null) + { + throw new ArgumentNullException(nameof(source11)); + } + + if (source12 == null) + { + throw new ArgumentNullException(nameof(source12)); + } + + if (source13 == null) + { + throw new ArgumentNullException(nameof(source13)); + } + + if (source14 == null) + { + throw new ArgumentNullException(nameof(source14)); + } + + if (source15 == null) + { + throw new ArgumentNullException(nameof(source15)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.Zip(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, source11, source12, source13, source14, source15, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the fourteenth source sequence. + /// The type of the elements in the fifteenth source sequence. + /// The type of the elements in the sixteenth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Thirteenth observable source. + /// Fourteenth observable source. + /// Fifteenth observable source. + /// Sixteenth observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or or or or or or or or or or or or or or or or is null. + public static IObservable Zip(this IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, IObservable source15, IObservable source16, Func resultSelector) + { + if (source1 == null) + { + throw new ArgumentNullException(nameof(source1)); + } + + if (source2 == null) + { + throw new ArgumentNullException(nameof(source2)); + } + + if (source3 == null) + { + throw new ArgumentNullException(nameof(source3)); + } + + if (source4 == null) + { + throw new ArgumentNullException(nameof(source4)); + } + + if (source5 == null) + { + throw new ArgumentNullException(nameof(source5)); + } + + if (source6 == null) + { + throw new ArgumentNullException(nameof(source6)); + } + + if (source7 == null) + { + throw new ArgumentNullException(nameof(source7)); + } + + if (source8 == null) + { + throw new ArgumentNullException(nameof(source8)); + } + + if (source9 == null) + { + throw new ArgumentNullException(nameof(source9)); + } + + if (source10 == null) + { + throw new ArgumentNullException(nameof(source10)); + } + + if (source11 == null) + { + throw new ArgumentNullException(nameof(source11)); + } + + if (source12 == null) + { + throw new ArgumentNullException(nameof(source12)); + } + + if (source13 == null) + { + throw new ArgumentNullException(nameof(source13)); + } + + if (source14 == null) + { + throw new ArgumentNullException(nameof(source14)); + } + + if (source15 == null) + { + throw new ArgumentNullException(nameof(source15)); + } + + if (source16 == null) + { + throw new ArgumentNullException(nameof(source16)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.Zip(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, source11, source12, source13, source14, source15, source16, resultSelector); + } + + } + +#pragma warning disable CA1711 // (Don't use Ex suffix.) This has been a public type for many years, so we can't rename it now. + public static partial class ObservableEx +#pragma warning restore CA1711 + { + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// First observable source. + /// Second observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or is null. + public static IObservable<(TFirst First, TSecond Second)> Zip(this IObservable first, IObservable second) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + + return s_impl.Zip(first, second); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or is null. + public static IObservable<(TFirst First, TSecond Second, TThird Third)> Zip(this IObservable first, IObservable second, IObservable third) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + + return s_impl.Zip(first, second, third); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or is null. + public static IObservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth)> Zip(this IObservable first, IObservable second, IObservable third, IObservable fourth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + + return s_impl.Zip(first, second, third, fourth); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or is null. + public static IObservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth)> Zip(this IObservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + + return s_impl.Zip(first, second, third, fourth, fifth); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or is null. + public static IObservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth)> Zip(this IObservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + + return s_impl.Zip(first, second, third, fourth, fifth, sixth); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or or is null. + public static IObservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh)> Zip(this IObservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + if (seventh == null) + throw new ArgumentNullException(nameof(seventh)); + + return s_impl.Zip(first, second, third, fourth, fifth, sixth, seventh); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or or or is null. + public static IObservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth)> Zip(this IObservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + if (seventh == null) + throw new ArgumentNullException(nameof(seventh)); + if (eighth == null) + throw new ArgumentNullException(nameof(eighth)); + + return s_impl.Zip(first, second, third, fourth, fifth, sixth, seventh, eighth); + } + + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable.Multiple.Zip.tt b/LibExternal/System.Reactive/Linq/Observable.Multiple.Zip.tt new file mode 100644 index 0000000..b070483 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable.Multiple.Zip.tt @@ -0,0 +1,133 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> +// This code was generated by a T4 template at <#=DateTime.Now#>. + +namespace System.Reactive.Linq +{ + public static partial class Observable + { +<# +Func toUpper = s => char.ToUpper(s[0]) + s.Substring(1); + +string[] ordinals = new[] { "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth", "tenth", "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", "sixteenth" }; + +for (int i = 3; i <= 16; i++) +{ + var parameters = string.Join(", ", Enumerable.Range(1, i).Select(j => "IObservable source" + j)); + var genArgs = string.Join(", ", Enumerable.Range(1, i).Select(j => "TSource" + j)); + var sources = string.Join(", ", Enumerable.Range(1, i).Select(j => "source" + j)); + var paramRefs = string.Join(" or ", Enumerable.Range(1, i).Select(j => "")); + +#> + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// +<# +for (int j = 0; j < i; j++) +{ +#> + /// The type of the elements in the <#=ordinals[j]#> source sequence. +<# +} +#> + /// The type of the elements in the result sequence, returned by the selector function. +<# +for (int j = 0; j < i; j++) +{ +#> + /// <#=toUpper(ordinals[j])#> observable source. +<# +} +#> + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// <#=paramRefs#> or is null. + public static IObservable Zip<<#=genArgs#>, TResult>(this <#=parameters#>, Func<<#=genArgs#>, TResult> resultSelector) + { +<# +for (int j = 0; j < i; j++) +{ +#> + if (source<#=j + 1#> == null) + { + throw new ArgumentNullException(nameof(source<#=j + 1#>)); + } + +<# +} +#> + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.Zip(<#=sources#>, resultSelector); + } + +<# +} +#> + } + + public static partial class ObservableEx + { +<# +for (int i = 2; i <= 8; i++) +{ + var parameters = string.Join(", ", Enumerable.Range(1, i).Select(j => "IObservable " + ordinals[j - 1])); + var genArgs = string.Join(", ", Enumerable.Range(1, i).Select(j => "T" + toUpper(ordinals[j - 1]))); + var sources = string.Join(", ", Enumerable.Range(1, i).Select(j => ordinals[j - 1])); + var paramRefs = string.Join(" or ", Enumerable.Range(1, i).Select(j => "")); + var tuple = "(" + string.Join(", ", Enumerable.Range(1, i).Select(j => "T" + toUpper(ordinals[j - 1]) + " " + toUpper(ordinals[j - 1]))) + ")"; + var vals = string.Join(", ", Enumerable.Range(1, i).Select(j => "t" + j)); + +#> + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever all of the observable sequences have produced an element at a corresponding index. + /// +<# +for (int j = 0; j < i; j++) +{ +#> + /// The type of the elements in the <#=ordinals[j]#> source sequence. +<# +} +#> +<# +for (int j = 0; j < i; j++) +{ +#> + /// <#=toUpper(ordinals[j])#> observable source. +<# +} +#> + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// <#=paramRefs#> is null. + public static IObservable<<#=tuple#>> Zip<<#=genArgs#>>(this <#=parameters#>) + { +<# +for (int j = 0; j < i; j++) +{ +#> + if (<#=ordinals[j]#> == null) + throw new ArgumentNullException(nameof(<#=ordinals[j]#>)); +<# +} +#> + + return s_impl.Zip(<#=sources#>); + } + +<# +} +#> + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable.Multiple.cs b/LibExternal/System.Reactive/Linq/Observable.Multiple.cs new file mode 100644 index 0000000..865f791 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable.Multiple.cs @@ -0,0 +1,1183 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Configuration; +using System.Reactive.Concurrency; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Reactive.Linq +{ + public static partial class Observable + { + #region + Amb + + + /// + /// Propagates the observable sequence that reacts first. + /// + /// The type of the elements in the source sequences. + /// First observable sequence. + /// Second observable sequence. + /// An observable sequence that surfaces either of the given sequences, whichever reacted first. + /// or is null. + [Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Amb", Justification = "In honor of McCarthy.")] + public static IObservable Amb(this IObservable first, IObservable second) + { + if (first == null) + { + throw new ArgumentNullException(nameof(first)); + } + + if (second == null) + { + throw new ArgumentNullException(nameof(second)); + } + + return s_impl.Amb(first, second); + } + + /// + /// Propagates the observable sequence that reacts first. + /// + /// The type of the elements in the source sequences. + /// Observable sources competing to react first. + /// An observable sequence that surfaces any of the given sequences, whichever reacted first. + /// is null. + [Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Amb", Justification = "In honor of McCarthy.")] + public static IObservable Amb(params IObservable[] sources) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + return s_impl.Amb(sources); + } + + /// + /// Propagates the observable sequence that reacts first. + /// + /// The type of the elements in the source sequences. + /// Observable sources competing to react first. + /// An observable sequence that surfaces any of the given sequences, whichever reacted first. + /// is null. + [Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Amb", Justification = "In honor of McCarthy.")] + public static IObservable Amb(this IEnumerable> sources) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + return s_impl.Amb(sources); + } + + #endregion + + #region + Buffer + + + /// + /// Projects each element of an observable sequence into consecutive non-overlapping buffers. + /// + /// The type of the elements in the source sequence, and in the lists in the result sequence. + /// The type of the elements in the sequences indicating buffer closing events. + /// Source sequence to produce buffers over. + /// A function invoked to define the boundaries of the produced buffers. A new buffer is started when the previous one is closed. + /// An observable sequence of buffers. + /// or is null. + public static IObservable> Buffer(this IObservable source, Func> bufferClosingSelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (bufferClosingSelector == null) + { + throw new ArgumentNullException(nameof(bufferClosingSelector)); + } + + return s_impl.Buffer(source, bufferClosingSelector); + } + + /// + /// Projects each element of an observable sequence into zero or more buffers. + /// + /// The type of the elements in the source sequence, and in the lists in the result sequence. + /// The type of the elements in the sequence indicating buffer opening events, also passed to the closing selector to obtain a sequence of buffer closing events. + /// The type of the elements in the sequences indicating buffer closing events. + /// Source sequence to produce buffers over. + /// Observable sequence whose elements denote the creation of new buffers. + /// A function invoked to define the closing of each produced buffer. + /// An observable sequence of buffers. + /// or or is null. + public static IObservable> Buffer(this IObservable source, IObservable bufferOpenings, Func> bufferClosingSelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (bufferOpenings == null) + { + throw new ArgumentNullException(nameof(bufferOpenings)); + } + + if (bufferClosingSelector == null) + { + throw new ArgumentNullException(nameof(bufferClosingSelector)); + } + + return s_impl.Buffer(source, bufferOpenings, bufferClosingSelector); + } + + /// + /// Projects each element of an observable sequence into consecutive non-overlapping buffers. + /// + /// The type of the elements in the source sequence, and in the lists in the result sequence. + /// The type of the elements in the sequences indicating buffer boundary events. + /// Source sequence to produce buffers over. + /// Sequence of buffer boundary markers. The current buffer is closed and a new buffer is opened upon receiving a boundary marker. + /// An observable sequence of buffers. + /// or is null. + public static IObservable> Buffer(this IObservable source, IObservable bufferBoundaries) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (bufferBoundaries == null) + { + throw new ArgumentNullException(nameof(bufferBoundaries)); + } + + return s_impl.Buffer(source, bufferBoundaries); + } + + #endregion + + #region + Catch + + + /// + /// Continues an observable sequence that is terminated by an exception of the specified type with the observable sequence produced by the handler. + /// + /// The type of the elements in the source sequence and sequences returned by the exception handler function. + /// The type of the exception to catch and handle. Needs to derive from . + /// Source sequence. + /// Exception handler function, producing another observable sequence. + /// An observable sequence containing the source sequence's elements, followed by the elements produced by the handler's resulting observable sequence in case an exception occurred. + /// or is null. + public static IObservable Catch(this IObservable source, Func> handler) where TException : Exception + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (handler == null) + { + throw new ArgumentNullException(nameof(handler)); + } + + return s_impl.Catch(source, handler); + } + + /// + /// Continues an observable sequence that is terminated by an exception with the next observable sequence. + /// + /// The type of the elements in the source sequence and handler sequence. + /// First observable sequence whose exception (if any) is caught. + /// Second observable sequence used to produce results when an error occurred in the first sequence. + /// An observable sequence containing the first sequence's elements, followed by the elements of the second sequence in case an exception occurred. + /// or is null. + public static IObservable Catch(this IObservable first, IObservable second) + { + if (first == null) + { + throw new ArgumentNullException(nameof(first)); + } + + if (second == null) + { + throw new ArgumentNullException(nameof(second)); + } + + return s_impl.Catch(first, second); + } + + /// + /// Continues an observable sequence that is terminated by an exception with the next observable sequence. + /// + /// The type of the elements in the source and handler sequences. + /// Observable sequences to catch exceptions for. + /// An observable sequence containing elements from consecutive source sequences until a source sequence terminates successfully. + /// is null. + public static IObservable Catch(params IObservable[] sources) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + return s_impl.Catch(sources); + } + + /// + /// Continues an observable sequence that is terminated by an exception with the next observable sequence. + /// + /// The type of the elements in the source and handler sequences. + /// Observable sequences to catch exceptions for. + /// An observable sequence containing elements from consecutive source sequences until a source sequence terminates successfully. + /// is null. + public static IObservable Catch(this IEnumerable> sources) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + return s_impl.Catch(sources); + } + + #endregion + + #region + CombineLatest + + + /// + /// Merges two observable sequences into one observable sequence by using the selector function whenever one of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Function to invoke whenever either of the sources produces an element. + /// An observable sequence containing the result of combining elements of both sources using the specified result selector function. + /// or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IObservable CombineLatest(this IObservable first, IObservable second, Func resultSelector) + { + if (first == null) + { + throw new ArgumentNullException(nameof(first)); + } + + if (second == null) + { + throw new ArgumentNullException(nameof(second)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.CombineLatest(first, second, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the source sequences. + /// The type of the elements in the result sequence, returned by the selector function. + /// Observable sources. + /// Function to invoke whenever any of the sources produces an element. For efficiency, the input list is reused after the selector returns. Either aggregate or copy the values during the function call. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IObservable CombineLatest(this IEnumerable> sources, Func, TResult> resultSelector) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.CombineLatest(sources, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by emitting a list with the latest source elements whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the source sequences, and in the lists in the result sequence. + /// Observable sources. + /// An observable sequence containing lists of the latest elements of the sources. + /// is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IObservable> CombineLatest(this IEnumerable> sources) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + return s_impl.CombineLatest(sources); + } + + /// + /// Merges the specified observable sequences into one observable sequence by emitting a list with the latest source elements whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the source sequences, and in the lists in the result sequence. + /// Observable sources. + /// An observable sequence containing lists of the latest elements of the sources. + /// is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IObservable> CombineLatest(params IObservable[] sources) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + return s_impl.CombineLatest(sources); + } + + #endregion + + #region + Concat + + + /// + /// Concatenates the second observable sequence to the first observable sequence upon successful termination of the first. + /// + /// The type of the elements in the source sequences. + /// First observable sequence. + /// Second observable sequence. + /// An observable sequence that contains the elements of the first sequence, followed by those of the second the sequence. + /// or is null. + public static IObservable Concat(this IObservable first, IObservable second) + { + if (first == null) + { + throw new ArgumentNullException(nameof(first)); + } + + if (second == null) + { + throw new ArgumentNullException(nameof(second)); + } + + return s_impl.Concat(first, second); + } + + /// + /// Concatenates all of the specified observable sequences, as long as the previous observable sequence terminated successfully. + /// + /// The type of the elements in the source sequences. + /// Observable sequences to concatenate. + /// An observable sequence that contains the elements of each given sequence, in sequential order. + /// is null. + public static IObservable Concat(params IObservable[] sources) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + return s_impl.Concat(sources); + } + + /// + /// Concatenates all observable sequences in the given enumerable sequence, as long as the previous observable sequence terminated successfully. + /// + /// The type of the elements in the source sequences. + /// Observable sequences to concatenate. + /// An observable sequence that contains the elements of each given sequence, in sequential order. + /// is null. + public static IObservable Concat(this IEnumerable> sources) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + return s_impl.Concat(sources); + } + + /// + /// Concatenates all inner observable sequences, as long as the previous observable sequence terminated successfully. + /// + /// The type of the elements in the source sequences. + /// Observable sequence of inner observable sequences. + /// An observable sequence that contains the elements of each observed inner sequence, in sequential order. + /// is null. + public static IObservable Concat(this IObservable> sources) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + return s_impl.Concat(sources); + } + + /// + /// Concatenates all task results, as long as the previous task terminated successfully. + /// + /// The type of the results produced by the tasks. + /// Observable sequence of tasks. + /// An observable sequence that contains the results of each task, in sequential order. + /// is null. + /// If the tasks support cancellation, consider manual conversion of the tasks using , followed by a concatenation operation using . + public static IObservable Concat(this IObservable> sources) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + return s_impl.Concat(sources); + } + + #endregion + + #region + Merge + + + /// + /// Merges elements from all inner observable sequences into a single observable sequence. + /// + /// The type of the elements in the source sequences. + /// Observable sequence of inner observable sequences. + /// The observable sequence that merges the elements of the inner sequences. + /// is null. + public static IObservable Merge(this IObservable> sources) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + return s_impl.Merge(sources); + } + + /// + /// Merges results from all source tasks into a single observable sequence. + /// + /// The type of the results produced by the source tasks. + /// Observable sequence of tasks. + /// The observable sequence that merges the results of the source tasks. + /// is null. + /// If the tasks support cancellation, consider manual conversion of the tasks using , followed by a merge operation using . + public static IObservable Merge(this IObservable> sources) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + return s_impl.Merge(sources); + } + + /// + /// Merges elements from all inner observable sequences into a single observable sequence, limiting the number of concurrent subscriptions to inner sequences. + /// + /// The type of the elements in the source sequences. + /// Observable sequence of inner observable sequences. + /// Maximum number of inner observable sequences being subscribed to concurrently. + /// The observable sequence that merges the elements of the inner sequences. + /// is null. + /// is less than or equal to zero. + public static IObservable Merge(this IObservable> sources, int maxConcurrent) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + if (maxConcurrent <= 0) + { + throw new ArgumentOutOfRangeException(nameof(maxConcurrent)); + } + + return s_impl.Merge(sources, maxConcurrent); + } + + /// + /// Merges elements from all observable sequences in the given enumerable sequence into a single observable sequence, limiting the number of concurrent subscriptions to inner sequences. + /// + /// The type of the elements in the source sequences. + /// Enumerable sequence of observable sequences. + /// Maximum number of observable sequences being subscribed to concurrently. + /// The observable sequence that merges the elements of the observable sequences. + /// is null. + /// is less than or equal to zero. + public static IObservable Merge(this IEnumerable> sources, int maxConcurrent) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + if (maxConcurrent <= 0) + { + throw new ArgumentOutOfRangeException(nameof(maxConcurrent)); + } + + return s_impl.Merge(sources, maxConcurrent); + } + + /// + /// Merges elements from all observable sequences in the given enumerable sequence into a single observable sequence, limiting the number of concurrent subscriptions to inner sequences, and using the specified scheduler for enumeration of and subscription to the sources. + /// + /// The type of the elements in the source sequences. + /// Enumerable sequence of observable sequences. + /// Maximum number of observable sequences being subscribed to concurrently. + /// Scheduler to run the enumeration of the sequence of sources on. + /// The observable sequence that merges the elements of the observable sequences. + /// or is null. + /// is less than or equal to zero. + public static IObservable Merge(this IEnumerable> sources, int maxConcurrent, IScheduler scheduler) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + if (maxConcurrent <= 0) + { + throw new ArgumentOutOfRangeException(nameof(maxConcurrent)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Merge(sources, maxConcurrent, scheduler); + } + + /// + /// Merges elements from two observable sequences into a single observable sequence. + /// + /// The type of the elements in the source sequences. + /// First observable sequence. + /// Second observable sequence. + /// The observable sequence that merges the elements of the given sequences. + /// or is null. + public static IObservable Merge(this IObservable first, IObservable second) + { + if (first == null) + { + throw new ArgumentNullException(nameof(first)); + } + + if (second == null) + { + throw new ArgumentNullException(nameof(second)); + } + + return s_impl.Merge(first, second); + } + + /// + /// Merges elements from two observable sequences into a single observable sequence, using the specified scheduler for enumeration of and subscription to the sources. + /// + /// The type of the elements in the source sequences. + /// First observable sequence. + /// Second observable sequence. + /// Scheduler used to introduce concurrency for making subscriptions to the given sequences. + /// The observable sequence that merges the elements of the given sequences. + /// or or is null. + public static IObservable Merge(this IObservable first, IObservable second, IScheduler scheduler) + { + if (first == null) + { + throw new ArgumentNullException(nameof(first)); + } + + if (second == null) + { + throw new ArgumentNullException(nameof(second)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Merge(first, second, scheduler); + } + + /// + /// Merges elements from all of the specified observable sequences into a single observable sequence. + /// + /// The type of the elements in the source sequences. + /// Observable sequences. + /// The observable sequence that merges the elements of the observable sequences. + /// is null. + public static IObservable Merge(params IObservable[] sources) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + return s_impl.Merge(sources); + } + + /// + /// Merges elements from all of the specified observable sequences into a single observable sequence, using the specified scheduler for enumeration of and subscription to the sources. + /// + /// The type of the elements in the source sequences. + /// Observable sequences. + /// Scheduler to run the enumeration of the sequence of sources on. + /// The observable sequence that merges the elements of the observable sequences. + /// or is null. + public static IObservable Merge(IScheduler scheduler, params IObservable[] sources) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + return s_impl.Merge(scheduler, sources); + } + + /// + /// Merges elements from all observable sequences in the given enumerable sequence into a single observable sequence. + /// + /// The type of the elements in the source sequences. + /// Enumerable sequence of observable sequences. + /// The observable sequence that merges the elements of the observable sequences. + /// is null. + public static IObservable Merge(this IEnumerable> sources) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + return s_impl.Merge(sources); + } + + /// + /// Merges elements from all observable sequences in the given enumerable sequence into a single observable sequence, using the specified scheduler for enumeration of and subscription to the sources. + /// + /// The type of the elements in the source sequences. + /// Enumerable sequence of observable sequences. + /// Scheduler to run the enumeration of the sequence of sources on. + /// The observable sequence that merges the elements of the observable sequences. + /// or is null. + public static IObservable Merge(this IEnumerable> sources, IScheduler scheduler) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Merge(sources, scheduler); + } + + #endregion + + #region + OnErrorResumeNext + + + /// + /// Concatenates the second observable sequence to the first observable sequence upon successful or exceptional termination of the first. + /// + /// The type of the elements in the source sequences. + /// First observable sequence whose exception (if any) is caught. + /// Second observable sequence used to produce results after the first sequence terminates. + /// An observable sequence that concatenates the first and second sequence, even if the first sequence terminates exceptionally. + /// or is null. + public static IObservable OnErrorResumeNext(this IObservable first, IObservable second) + { + if (first == null) + { + throw new ArgumentNullException(nameof(first)); + } + + if (second == null) + { + throw new ArgumentNullException(nameof(second)); + } + + return s_impl.OnErrorResumeNext(first, second); + } + + /// + /// Concatenates all of the specified observable sequences, even if the previous observable sequence terminated exceptionally. + /// + /// The type of the elements in the source sequences. + /// Observable sequences to concatenate. + /// An observable sequence that concatenates the source sequences, even if a sequence terminates exceptionally. + /// is null. + public static IObservable OnErrorResumeNext(params IObservable[] sources) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + return s_impl.OnErrorResumeNext(sources); + } + + /// + /// Concatenates all observable sequences in the given enumerable sequence, even if the previous observable sequence terminated exceptionally. + /// + /// The type of the elements in the source sequences. + /// Observable sequences to concatenate. + /// An observable sequence that concatenates the source sequences, even if a sequence terminates exceptionally. + /// is null. + public static IObservable OnErrorResumeNext(this IEnumerable> sources) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + return s_impl.OnErrorResumeNext(sources); + } + + #endregion + + #region + SkipUntil + + + /// + /// Returns the elements from the source observable sequence only after the other observable sequence produces an element. + /// Starting from Rx.NET 4.0, this will subscribe to before subscribing to + /// so in case emits an element right away, elements from are not missed. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the other sequence that indicates the end of skip behavior. + /// Source sequence to propagate elements for. + /// Observable sequence that triggers propagation of elements of the source sequence. + /// An observable sequence containing the elements of the source sequence starting from the point the other sequence triggered propagation. + /// or is null. + public static IObservable SkipUntil(this IObservable source, IObservable other) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return s_impl.SkipUntil(source, other); + } + + #endregion + + #region + Switch + + + /// + /// Transforms an observable sequence of observable sequences into an observable sequence + /// producing values only from the most recent observable sequence. + /// Each time a new inner observable sequence is received, unsubscribe from the + /// previous inner observable sequence. + /// + /// The type of the elements in the source sequences. + /// Observable sequence of inner observable sequences. + /// The observable sequence that at any point in time produces the elements of the most recent inner observable sequence that has been received. + /// is null. + public static IObservable Switch(this IObservable> sources) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + return s_impl.Switch(sources); + } + + /// + /// Transforms an observable sequence of tasks into an observable sequence + /// producing values only from the most recent observable sequence. + /// Each time a new task is received, the previous task's result is ignored. + /// + /// The type of the results produced by the source tasks. + /// Observable sequence of tasks. + /// The observable sequence that at any point in time produces the result of the most recent task that has been received. + /// is null. + /// If the tasks support cancellation, consider manual conversion of the tasks using , followed by a switch operation using . + public static IObservable Switch(this IObservable> sources) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + return s_impl.Switch(sources); + } + + #endregion + + #region + TakeUntil + + + /// + /// Returns the elements from the source observable sequence until the other observable sequence produces an element. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the other sequence that indicates the end of take behavior. + /// Source sequence to propagate elements for. + /// Observable sequence that terminates propagation of elements of the source sequence. + /// An observable sequence containing the elements of the source sequence up to the point the other sequence interrupted further propagation. + /// or is null. + public static IObservable TakeUntil(this IObservable source, IObservable other) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return s_impl.TakeUntil(source, other); + } + + /// + /// Relays elements from the source observable sequence and calls the predicate after an + /// emission to check if the sequence should stop after that specific item. + /// + /// The type of the elements in the source and result sequences. + /// The source sequence to relay elements of. + /// Called after each upstream item has been emitted with + /// that upstream item and should return true to indicate the sequence should + /// complete. + /// The observable sequence with the source elements until the stop predicate returns true. + /// + /// The following sequence will stop after the value 5 has been encountered: + /// + /// Observable.Range(1, 10) + /// .TakeUntil(item => item == 5) + /// .Subscribe(Console.WriteLine); + /// + /// + /// If or is null. + public static IObservable TakeUntil(this IObservable source, Func stopPredicate) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (stopPredicate == null) + { + throw new ArgumentNullException(nameof(stopPredicate)); + } + + return s_impl.TakeUntil(source, stopPredicate); + } + + #endregion + + #region + Window + + + /// + /// Projects each element of an observable sequence into consecutive non-overlapping windows. + /// + /// The type of the elements in the source sequence, and in the windows in the result sequence. + /// The type of the elements in the sequences indicating window closing events. + /// Source sequence to produce windows over. + /// A function invoked to define the boundaries of the produced windows. A new window is started when the previous one is closed. + /// An observable sequence of windows. + /// or is null. + public static IObservable> Window(this IObservable source, Func> windowClosingSelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (windowClosingSelector == null) + { + throw new ArgumentNullException(nameof(windowClosingSelector)); + } + + return s_impl.Window(source, windowClosingSelector); + } + + /// + /// Projects each element of an observable sequence into zero or more windows. + /// + /// The type of the elements in the source sequence, and in the windows in the result sequence. + /// The type of the elements in the sequence indicating window opening events, also passed to the closing selector to obtain a sequence of window closing events. + /// The type of the elements in the sequences indicating window closing events. + /// Source sequence to produce windows over. + /// Observable sequence whose elements denote the creation of new windows. + /// A function invoked to define the closing of each produced window. + /// An observable sequence of windows. + /// or or is null. + public static IObservable> Window(this IObservable source, IObservable windowOpenings, Func> windowClosingSelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (windowOpenings == null) + { + throw new ArgumentNullException(nameof(windowOpenings)); + } + + if (windowClosingSelector == null) + { + throw new ArgumentNullException(nameof(windowClosingSelector)); + } + + return s_impl.Window(source, windowOpenings, windowClosingSelector); + } + + /// + /// Projects each element of an observable sequence into consecutive non-overlapping windows. + /// + /// The type of the elements in the source sequence, and in the windows in the result sequence. + /// The type of the elements in the sequences indicating window boundary events. + /// Source sequence to produce windows over. + /// Sequence of window boundary markers. The current window is closed and a new window is opened upon receiving a boundary marker. + /// An observable sequence of windows. + /// or is null. + public static IObservable> Window(this IObservable source, IObservable windowBoundaries) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (windowBoundaries == null) + { + throw new ArgumentNullException(nameof(windowBoundaries)); + } + + return s_impl.Window(source, windowBoundaries); + } + + #endregion + + #region + WithLatestFrom + + + /// + /// Merges two observable sequences into one observable sequence by combining each element from the first source with the latest element from the second source, if any. + /// Starting from Rx.NET 4.0, this will subscribe to before subscribing to to have a latest element readily available + /// in case emits an element right away. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Function to invoke for each element from the first source combined with the latest element from the second source, if any. + /// An observable sequence containing the result of combining each element of the first source with the latest element from the second source, if any, using the specified result selector function. + /// or or is null. + public static IObservable WithLatestFrom(this IObservable first, IObservable second, Func resultSelector) + { + if (first == null) + { + throw new ArgumentNullException(nameof(first)); + } + + if (second == null) + { + throw new ArgumentNullException(nameof(second)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.WithLatestFrom(first, second, resultSelector); + } + + #endregion + + #region + Zip + + + /// + /// Merges two observable sequences into one observable sequence by combining their elements in a pairwise fashion. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Function to invoke for each consecutive pair of elements from the first and second source. + /// An observable sequence containing the result of pairwise combining the elements of the first and second source using the specified result selector function. + /// or or is null. + public static IObservable Zip(this IObservable first, IObservable second, Func resultSelector) + { + if (first == null) + { + throw new ArgumentNullException(nameof(first)); + } + + if (second == null) + { + throw new ArgumentNullException(nameof(second)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.Zip(first, second, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the source sequences. + /// The type of the elements in the result sequence, returned by the selector function. + /// Observable sources. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// or is null. + public static IObservable Zip(this IEnumerable> sources, Func, TResult> resultSelector) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.Zip(sources, resultSelector); + } + + /// + /// Merges the specified observable sequences into one observable sequence by emitting a list with the elements of the observable sequences at corresponding indexes. + /// + /// The type of the elements in the source sequences, and in the lists in the result sequence. + /// Observable sources. + /// An observable sequence containing lists of elements at corresponding indexes. + /// is null. + public static IObservable> Zip(this IEnumerable> sources) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + return s_impl.Zip(sources); + } + + /// + /// Merges the specified observable sequences into one observable sequence by emitting a list with the elements of the observable sequences at corresponding indexes. + /// + /// The type of the elements in the source sequences, and in the lists in the result sequence. + /// Observable sources. + /// An observable sequence containing lists of elements at corresponding indexes. + /// is null. + public static IObservable> Zip(params IObservable[] sources) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + return s_impl.Zip(sources); + } + + /// + /// Merges an observable sequence and an enumerable sequence into one observable sequence by using the selector function. + /// + /// The type of the elements in the first observable source sequence. + /// The type of the elements in the second enumerable source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second enumerable source. + /// Function to invoke for each consecutive pair of elements from the first and second source. + /// An observable sequence containing the result of pairwise combining the elements of the first and second source using the specified result selector function. + /// or or is null. + public static IObservable Zip(this IObservable first, IEnumerable second, Func resultSelector) + { + if (first == null) + { + throw new ArgumentNullException(nameof(first)); + } + + if (second == null) + { + throw new ArgumentNullException(nameof(second)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.Zip(first, second, resultSelector); + } + + #endregion + } + +#pragma warning disable CA1711 // (Don't use Ex suffix.) This has been a public type for many years, so we can't rename it now. + public static partial class ObservableEx +#pragma warning restore CA1711 + { + /// + /// Merges two observable sequences into one observable sequence by combining each element from the first source with the latest element from the second source, if any. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// First observable source. + /// Second observable source. + /// An observable sequence containing the result of combining each element of the first source with the latest element from the second source, if any, as a tuple value. + /// or is null. + public static IObservable<(TFirst First, TSecond Second)> WithLatestFrom(this IObservable first, IObservable second) + { + if (first == null) + { + throw new ArgumentNullException(nameof(first)); + } + + if (second == null) + { + throw new ArgumentNullException(nameof(second)); + } + + return s_impl.WithLatestFrom(first, second); + } + + /// + /// Merges an observable sequence and an enumerable sequence into one observable sequence of tuple values. + /// + /// The type of the elements in the first observable source sequence. + /// The type of the elements in the second enumerable source sequence. + /// First observable source. + /// Second enumerable source. + /// An observable sequence containing the result of pairwise combining the elements of the first and second source as a tuple value. + /// or is null. + public static IObservable<(TFirst First, TSecond Second)> Zip(this IObservable first, IEnumerable second) + { + if (first == null) + { + throw new ArgumentNullException(nameof(first)); + } + + if (second == null) + { + throw new ArgumentNullException(nameof(second)); + } + + return s_impl.Zip(first, second); + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable.Queryable.cs b/LibExternal/System.Reactive/Linq/Observable.Queryable.cs new file mode 100644 index 0000000..c2ff1a9 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable.Queryable.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +#if HAS_TRIMMABILITY_ATTRIBUTES +using System.Diagnostics.CodeAnalysis; +#endif + +namespace System.Reactive.Linq +{ +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.AsQueryableTrimIncompatibilityMessage)] +#endif + public static partial class Qbservable + { +#pragma warning disable IDE1006 // Naming Styles: 3rd party code is known to reflect for this specific field name + private static IQbservableProvider? s_provider; +#pragma warning restore IDE1006 // Naming Styles + + /// + /// Gets the local query provider which will retarget Qbservable-based queries to the corresponding Observable-based query for in-memory execution upon subscription. + /// + public static IQbservableProvider Provider + { + get + { + s_provider ??= new ObservableQueryProvider(); + + return s_provider; + } + } + + /// + /// Converts an in-memory observable sequence into an sequence with an expression tree representing the source sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// sequence representing the given observable source sequence. + /// is null. + public static IQbservable AsQbservable(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return new ObservableQuery(source); + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable.Single.cs b/LibExternal/System.Reactive/Linq/Observable.Single.cs new file mode 100644 index 0000000..89747d8 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable.Single.cs @@ -0,0 +1,1024 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Concurrency; + +namespace System.Reactive.Linq +{ + public static partial class Observable + { + #region + Append + + + /// + /// Append a value to an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to append the value to. + /// Value to append to the specified sequence. + /// The source sequence appended with the specified value. + /// is null. + public static IObservable Append(this IObservable source, TSource value) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Append(source, value); + } + + /// + /// Append a value to an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to append the value to. + /// Value to append to the specified sequence. + /// Scheduler to emit the append values on. + /// The source sequence appended with the specified value. + /// is null. + public static IObservable Append(this IObservable source, TSource value, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Append(source, value, scheduler); + } + + #endregion + + #region + AsObservable + + + /// + /// Hides the identity of an observable sequence. + /// + /// The type of the elements in the source sequence. + /// An observable sequence whose identity to hide. + /// An observable sequence that hides the identity of the source sequence. + /// is null. + public static IObservable AsObservable(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.AsObservable(source); + } + + #endregion + + #region + Buffer + + + /// + /// Projects each element of an observable sequence into consecutive non-overlapping buffers which are produced based on element count information. + /// + /// The type of the elements in the source sequence, and in the lists in the result sequence. + /// Source sequence to produce buffers over. + /// Length of each buffer. + /// An observable sequence of buffers. + /// is null. + /// is less than or equal to zero. + public static IObservable> Buffer(this IObservable source, int count) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (count <= 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + return s_impl.Buffer(source, count); + } + + /// + /// Projects each element of an observable sequence into zero or more buffers which are produced based on element count information. + /// + /// The type of the elements in the source sequence, and in the lists in the result sequence. + /// Source sequence to produce buffers over. + /// Length of each buffer. + /// Number of elements to skip between creation of consecutive buffers. + /// An observable sequence of buffers. + /// is null. + /// or is less than or equal to zero. + public static IObservable> Buffer(this IObservable source, int count, int skip) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (count <= 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (skip <= 0) + { + throw new ArgumentOutOfRangeException(nameof(skip)); + } + + return s_impl.Buffer(source, count, skip); + } + + #endregion + + #region + Dematerialize + + + /// + /// Dematerializes the explicit notification values of an observable sequence as implicit notifications. + /// + /// The type of the elements materialized in the source sequence notification objects. + /// An observable sequence containing explicit notification values which have to be turned into implicit notifications. + /// An observable sequence exhibiting the behavior corresponding to the source sequence's notification values. + /// is null. + public static IObservable Dematerialize(this IObservable> source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Dematerialize(source); + } + + #endregion + + #region + DistinctUntilChanged + + + /// + /// Returns an observable sequence that contains only distinct contiguous elements. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to retain distinct contiguous elements for. + /// An observable sequence only containing the distinct contiguous elements from the source sequence. + /// is null. + public static IObservable DistinctUntilChanged(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.DistinctUntilChanged(source); + } + + /// + /// Returns an observable sequence that contains only distinct contiguous elements according to the comparer. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to retain distinct contiguous elements for. + /// Equality comparer for source elements. + /// An observable sequence only containing the distinct contiguous elements from the source sequence. + /// or is null. + public static IObservable DistinctUntilChanged(this IObservable source, IEqualityComparer comparer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.DistinctUntilChanged(source, comparer); + } + + /// + /// Returns an observable sequence that contains only distinct contiguous elements according to the keySelector. + /// + /// The type of the elements in the source sequence. + /// The type of the discriminator key computed for each element in the source sequence. + /// An observable sequence to retain distinct contiguous elements for, based on a computed key value. + /// A function to compute the comparison key for each element. + /// An observable sequence only containing the distinct contiguous elements, based on a computed key value, from the source sequence. + /// or is null. + public static IObservable DistinctUntilChanged(this IObservable source, Func keySelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + return s_impl.DistinctUntilChanged(source, keySelector); + } + + /// + /// Returns an observable sequence that contains only distinct contiguous elements according to the keySelector and the comparer. + /// + /// The type of the elements in the source sequence. + /// The type of the discriminator key computed for each element in the source sequence. + /// An observable sequence to retain distinct contiguous elements for, based on a computed key value. + /// A function to compute the comparison key for each element. + /// Equality comparer for computed key values. + /// An observable sequence only containing the distinct contiguous elements, based on a computed key value, from the source sequence. + /// or or is null. + public static IObservable DistinctUntilChanged(this IObservable source, Func keySelector, IEqualityComparer comparer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.DistinctUntilChanged(source, keySelector, comparer); + } + + #endregion + + #region + Do + + + /// + /// Invokes an action for each element in the observable sequence, and propagates all observer messages through the result sequence. + /// This method can be used for debugging, logging, etc. of query behavior by intercepting the message stream to run arbitrary actions for messages on the pipeline. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Action to invoke for each element in the observable sequence. + /// The source sequence with the side-effecting behavior applied. + /// or is null. + public static IObservable Do(this IObservable source, Action onNext) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + return s_impl.Do(source, onNext); + } + + /// + /// Invokes an action for each element in the observable sequence and invokes an action upon graceful termination of the observable sequence. + /// This method can be used for debugging, logging, etc. of query behavior by intercepting the message stream to run arbitrary actions for messages on the pipeline. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Action to invoke for each element in the observable sequence. + /// Action to invoke upon graceful termination of the observable sequence. + /// The source sequence with the side-effecting behavior applied. + /// or or is null. + public static IObservable Do(this IObservable source, Action onNext, Action onCompleted) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + if (onCompleted == null) + { + throw new ArgumentNullException(nameof(onCompleted)); + } + + return s_impl.Do(source, onNext, onCompleted); + } + + /// + /// Invokes an action for each element in the observable sequence and invokes an action upon exceptional termination of the observable sequence. + /// This method can be used for debugging, logging, etc. of query behavior by intercepting the message stream to run arbitrary actions for messages on the pipeline. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Action to invoke for each element in the observable sequence. + /// Action to invoke upon exceptional termination of the observable sequence. + /// The source sequence with the side-effecting behavior applied. + /// or or is null. + public static IObservable Do(this IObservable source, Action onNext, Action onError) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + if (onError == null) + { + throw new ArgumentNullException(nameof(onError)); + } + + return s_impl.Do(source, onNext, onError); + } + + /// + /// Invokes an action for each element in the observable sequence and invokes an action upon graceful or exceptional termination of the observable sequence. + /// This method can be used for debugging, logging, etc. of query behavior by intercepting the message stream to run arbitrary actions for messages on the pipeline. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Action to invoke for each element in the observable sequence. + /// Action to invoke upon exceptional termination of the observable sequence. + /// Action to invoke upon graceful termination of the observable sequence. + /// The source sequence with the side-effecting behavior applied. + /// or or or is null. + public static IObservable Do(this IObservable source, Action onNext, Action onError, Action onCompleted) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + if (onError == null) + { + throw new ArgumentNullException(nameof(onError)); + } + + if (onCompleted == null) + { + throw new ArgumentNullException(nameof(onCompleted)); + } + + return s_impl.Do(source, onNext, onError, onCompleted); + } + + /// + /// Invokes the observer's methods for each message in the source sequence. + /// This method can be used for debugging, logging, etc. of query behavior by intercepting the message stream to run arbitrary actions for messages on the pipeline. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Observer whose methods to invoke as part of the source sequence's observation. + /// The source sequence with the side-effecting behavior applied. + /// or is null. + public static IObservable Do(this IObservable source, IObserver observer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + return s_impl.Do(source, observer); + } + + #endregion + + #region + Finally + + + /// + /// Invokes a specified action after the source observable sequence terminates gracefully or exceptionally. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Action to invoke after the source observable sequence terminates. + /// Source sequence with the action-invoking termination behavior applied. + /// or is null. + public static IObservable Finally(this IObservable source, Action finallyAction) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (finallyAction == null) + { + throw new ArgumentNullException(nameof(finallyAction)); + } + + return s_impl.Finally(source, finallyAction); + } + + #endregion + + #region + IgnoreElements + + + /// + /// Ignores all elements in an observable sequence leaving only the termination messages. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// An empty observable sequence that signals termination, successful or exceptional, of the source sequence. + /// is null. + public static IObservable IgnoreElements(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.IgnoreElements(source); + } + + #endregion + + #region + Materialize + + + /// + /// Materializes the implicit notifications of an observable sequence as explicit notification values. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to get notification values for. + /// An observable sequence containing the materialized notification values from the source sequence. + /// is null. + public static IObservable> Materialize(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Materialize(source); + } + + #endregion + + #region + Prepend + + + /// + /// Prepend a value to an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to prepend the value to. + /// Value to prepend to the specified sequence. + /// The source sequence prepended with the specified value. + /// is null. + public static IObservable Prepend(this IObservable source, TSource value) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Prepend(source, value); + } + + /// + /// Prepend a value to an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to prepend the value to. + /// Value to prepend to the specified sequence. + /// Scheduler to emit the prepend values on. + /// The source sequence prepended with the specified value. + /// is null. + public static IObservable Prepend(this IObservable source, TSource value, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Prepend(source, value, scheduler); + } + + #endregion + + #region + Repeat + + + /// + /// Repeats the observable sequence indefinitely. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to repeat. + /// The observable sequence producing the elements of the given sequence repeatedly and sequentially. + /// is null. + public static IObservable Repeat(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Repeat(source); + } + + /// + /// Repeats the observable sequence a specified number of times. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to repeat. + /// Number of times to repeat the sequence. + /// The observable sequence producing the elements of the given sequence repeatedly. + /// is null. + /// is less than zero. + public static IObservable Repeat(this IObservable source, int repeatCount) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (repeatCount < 0) + { + throw new ArgumentOutOfRangeException(nameof(repeatCount)); + } + + return s_impl.Repeat(source, repeatCount); + } + + /// + /// Repeatedly resubscribes to the source observable after a normal completion and when the observable + /// returned by a handler produces an arbitrary item. + /// + /// The type of the elements in the source sequence. + /// The arbitrary element type signaled by the handler observable. + /// Observable sequence to keep repeating when it successfully terminates. + /// The function that is called for each observer and takes an observable sequence of objects. + /// It should return an observable of arbitrary items that should signal that arbitrary item in + /// response to receiving the completion signal from the source observable. If this observable signals + /// a terminal event, the sequence is terminated with that signal instead. + /// An observable sequence producing the elements of the given sequence repeatedly while each repetition terminates successfully. + /// is null. + /// is null. + public static IObservable RepeatWhen(this IObservable source, Func, IObservable> handler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (handler == null) + { + throw new ArgumentNullException(nameof(handler)); + } + + return s_impl.RepeatWhen(source, handler); + } + + + #endregion + + #region + Retry + + + /// + /// Repeats the source observable sequence until it successfully terminates. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to repeat until it successfully terminates. + /// An observable sequence producing the elements of the given sequence repeatedly until it terminates successfully. + /// is null. + public static IObservable Retry(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Retry(source); + } + + /// + /// Repeats the source observable sequence the specified number of times or until it successfully terminates. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to repeat until it successfully terminates. + /// Number of times to repeat the sequence. + /// An observable sequence producing the elements of the given sequence repeatedly until it terminates successfully. + /// is null. + /// is less than zero. + public static IObservable Retry(this IObservable source, int retryCount) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (retryCount < 0) + { + throw new ArgumentOutOfRangeException(nameof(retryCount)); + } + + return s_impl.Retry(source, retryCount); + } + + /// + /// Retries (resubscribes to) the source observable after a failure and when the observable + /// returned by a handler produces an arbitrary item. + /// + /// The type of the elements in the source sequence. + /// The arbitrary element type signaled by the handler observable. + /// Observable sequence to repeat until it successfully terminates. + /// The function that is called for each observer and takes an observable sequence of + /// errors. It should return an observable of arbitrary items that should signal that arbitrary item in + /// response to receiving the failure Exception from the source observable. If this observable signals + /// a terminal event, the sequence is terminated with that signal instead. + /// An observable sequence producing the elements of the given sequence repeatedly until it terminates successfully. + /// is null. + /// is null. + public static IObservable RetryWhen(this IObservable source, Func, IObservable> handler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (handler == null) + { + throw new ArgumentNullException(nameof(handler)); + } + + return s_impl.RetryWhen(source, handler); + } + + + #endregion + + #region + Scan + + + /// + /// Applies an accumulator function over an observable sequence and returns each intermediate result. The specified seed value is used as the initial accumulator value. + /// For aggregation behavior with no intermediate results, see . + /// + /// The type of the elements in the source sequence. + /// The type of the result of the aggregation. + /// An observable sequence to accumulate over. + /// The initial accumulator value. + /// An accumulator function to be invoked on each element. + /// An observable sequence containing the accumulated values. + /// or is null. + public static IObservable Scan(this IObservable source, TAccumulate seed, Func accumulator) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (accumulator == null) + { + throw new ArgumentNullException(nameof(accumulator)); + } + + return s_impl.Scan(source, seed, accumulator); + } + + /// + /// Applies an accumulator function over an observable sequence and returns each intermediate result. + /// For aggregation behavior with no intermediate results, see . + /// + /// The type of the elements in the source sequence and the result of the aggregation. + /// An observable sequence to accumulate over. + /// An accumulator function to be invoked on each element. + /// An observable sequence containing the accumulated values. + /// or is null. + public static IObservable Scan(this IObservable source, Func accumulator) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (accumulator == null) + { + throw new ArgumentNullException(nameof(accumulator)); + } + + return s_impl.Scan(source, accumulator); + } + + #endregion + + #region + SkipLast + + + /// + /// Bypasses a specified number of elements at the end of an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Number of elements to bypass at the end of the source sequence. + /// An observable sequence containing the source sequence elements except for the bypassed ones at the end. + /// is null. + /// is less than zero. + /// + /// This operator accumulates a queue with a length enough to store the first elements. As more elements are + /// received, elements are taken from the front of the queue and produced on the result sequence. This causes elements to be delayed. + /// + public static IObservable SkipLast(this IObservable source, int count) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + return s_impl.SkipLast(source, count); + } + + #endregion + + #region + StartWith + + + /// + /// Prepends a sequence of values to an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to prepend values to. + /// Values to prepend to the specified sequence. + /// The source sequence prepended with the specified values. + /// or is null. + public static IObservable StartWith(this IObservable source, params TSource[] values) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + return s_impl.StartWith(source, values); + } + + /// + /// Prepends a sequence of values to an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to prepend values to. + /// Values to prepend to the specified sequence. + /// The source sequence prepended with the specified values. + /// or is null. + public static IObservable StartWith(this IObservable source, IEnumerable values) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + return s_impl.StartWith(source, values); + } + + /// + /// Prepends a sequence of values to an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to prepend values to. + /// Scheduler to emit the prepended values on. + /// Values to prepend to the specified sequence. + /// The source sequence prepended with the specified values. + /// or or is null. + public static IObservable StartWith(this IObservable source, IScheduler scheduler, params TSource[] values) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + return s_impl.StartWith(source, scheduler, values); + } + + /// + /// Prepends a sequence of values to an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to prepend values to. + /// Scheduler to emit the prepended values on. + /// Values to prepend to the specified sequence. + /// The source sequence prepended with the specified values. + /// or or is null. + public static IObservable StartWith(this IObservable source, IScheduler scheduler, IEnumerable values) + { + // + // NOTE: For some reason, someone introduced this signature which is inconsistent with the Rx pattern of putting the IScheduler last. + // We can't change it at this point because of compatibility. + // + + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + return s_impl.StartWith(source, scheduler, values); + } + + #endregion + + #region + TakeLast + + + /// + /// Returns a specified number of contiguous elements from the end of an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Number of elements to take from the end of the source sequence. + /// An observable sequence containing the specified number of elements from the end of the source sequence. + /// is null. + /// is less than zero. + /// + /// This operator accumulates a buffer with a length enough to store elements elements. Upon completion of + /// the source sequence, this buffer is drained on the result sequence. This causes the elements to be delayed. + /// + public static IObservable TakeLast(this IObservable source, int count) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + return s_impl.TakeLast(source, count); + } + + /// + /// Returns a specified number of contiguous elements from the end of an observable sequence, using the specified scheduler to drain the queue. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Number of elements to take from the end of the source sequence. + /// Scheduler used to drain the queue upon completion of the source sequence. + /// An observable sequence containing the specified number of elements from the end of the source sequence. + /// or is null. + /// is less than zero. + /// + /// This operator accumulates a buffer with a length enough to store elements elements. Upon completion of + /// the source sequence, this buffer is drained on the result sequence. This causes the elements to be delayed. + /// + public static IObservable TakeLast(this IObservable source, int count, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.TakeLast(source, count, scheduler); + } + + #endregion + + #region + TakeLastBuffer + + + /// + /// Returns a list with the specified number of contiguous elements from the end of an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Number of elements to take from the end of the source sequence. + /// An observable sequence containing a single list with the specified number of elements from the end of the source sequence. + /// is null. + /// is less than zero. + /// + /// This operator accumulates a buffer with a length enough to store elements. Upon completion of the + /// source sequence, this buffer is produced on the result sequence. + /// + public static IObservable> TakeLastBuffer(this IObservable source, int count) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + return s_impl.TakeLastBuffer(source, count); + } + + #endregion + + #region + Window + + + /// + /// Projects each element of an observable sequence into consecutive non-overlapping windows which are produced based on element count information. + /// + /// The type of the elements in the source sequence, and in the windows in the result sequence. + /// Source sequence to produce windows over. + /// Length of each window. + /// An observable sequence of windows. + /// is null. + /// is less than or equal to zero. + public static IObservable> Window(this IObservable source, int count) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (count <= 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + return s_impl.Window(source, count); + } + + /// + /// Projects each element of an observable sequence into zero or more windows which are produced based on element count information. + /// + /// The type of the elements in the source sequence, and in the windows in the result sequence. + /// Source sequence to produce windows over. + /// Length of each window. + /// Number of elements to skip between creation of consecutive windows. + /// An observable sequence of windows. + /// is null. + /// or is less than or equal to zero. + public static IObservable> Window(this IObservable source, int count, int skip) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (count <= 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (skip <= 0) + { + throw new ArgumentOutOfRangeException(nameof(skip)); + } + + return s_impl.Window(source, count, skip); + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable.StandardSequenceOperators.cs b/LibExternal/System.Reactive/Linq/Observable.StandardSequenceOperators.cs new file mode 100644 index 0000000..85d0e08 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable.StandardSequenceOperators.cs @@ -0,0 +1,1786 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Concurrency; +using System.Reactive.Threading.Tasks; // needed for doc comments +using System.Threading; +using System.Threading.Tasks; + +namespace System.Reactive.Linq +{ + public static partial class Observable + { + #region + Cast + + + /// + /// Converts the elements of an observable sequence to the specified type. + /// + /// The type to convert the elements in the source sequence to. + /// The observable sequence that contains the elements to be converted. + /// An observable sequence that contains each element of the source sequence converted to the specified type. + /// is null. + public static IObservable Cast(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Cast(source); + } + + #endregion + + #region + DefaultIfEmpty + + + /// + /// Returns the elements of the specified sequence or the type parameter's default value in a singleton sequence if the sequence is empty. + /// + /// The type of the elements in the source sequence (if any), whose default value will be taken if the sequence is empty. + /// The sequence to return a default value for if it is empty. + /// An observable sequence that contains the default value for the TSource type if the source is empty; otherwise, the elements of the source itself. + /// is null. + public static IObservable DefaultIfEmpty(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.DefaultIfEmpty(source); + } + + /// + /// Returns the elements of the specified sequence or the specified value in a singleton sequence if the sequence is empty. + /// + /// The type of the elements in the source sequence (if any), and the specified default value which will be taken if the sequence is empty. + /// The sequence to return the specified value for if it is empty. + /// The value to return if the sequence is empty. + /// An observable sequence that contains the specified default value if the source is empty; otherwise, the elements of the source itself. + /// is null. + public static IObservable DefaultIfEmpty(this IObservable source, TSource defaultValue) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.DefaultIfEmpty(source, defaultValue); + } + + #endregion + + #region + Distinct + + + /// + /// Returns an observable sequence that contains only distinct elements. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to retain distinct elements for. + /// An observable sequence only containing the distinct elements from the source sequence. + /// is null. + /// Usage of this operator should be considered carefully due to the maintenance of an internal lookup structure which can grow large. + public static IObservable Distinct(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Distinct(source); + } + + /// + /// Returns an observable sequence that contains only distinct elements according to the comparer. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to retain distinct elements for. + /// Equality comparer for source elements. + /// An observable sequence only containing the distinct elements from the source sequence. + /// or is null. + /// Usage of this operator should be considered carefully due to the maintenance of an internal lookup structure which can grow large. + public static IObservable Distinct(this IObservable source, IEqualityComparer comparer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.Distinct(source, comparer); + } + + /// + /// Returns an observable sequence that contains only distinct elements according to the keySelector. + /// + /// The type of the elements in the source sequence. + /// The type of the discriminator key computed for each element in the source sequence. + /// An observable sequence to retain distinct elements for. + /// A function to compute the comparison key for each element. + /// An observable sequence only containing the distinct elements, based on a computed key value, from the source sequence. + /// or is null. + /// Usage of this operator should be considered carefully due to the maintenance of an internal lookup structure which can grow large. + public static IObservable Distinct(this IObservable source, Func keySelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + return s_impl.Distinct(source, keySelector); + } + + /// + /// Returns an observable sequence that contains only distinct elements according to the keySelector and the comparer. + /// + /// The type of the elements in the source sequence. + /// The type of the discriminator key computed for each element in the source sequence. + /// An observable sequence to retain distinct elements for. + /// A function to compute the comparison key for each element. + /// Equality comparer for source elements. + /// An observable sequence only containing the distinct elements, based on a computed key value, from the source sequence. + /// or or is null. + /// Usage of this operator should be considered carefully due to the maintenance of an internal lookup structure which can grow large. + public static IObservable Distinct(this IObservable source, Func keySelector, IEqualityComparer comparer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.Distinct(source, keySelector, comparer); + } + + #endregion + + #region + GroupBy + + + /// + /// Groups the elements of an observable sequence according to a specified key selector function. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// or is null. + public static IObservable> GroupBy(this IObservable source, Func keySelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + return s_impl.GroupBy(source, keySelector); + } + + /// + /// Groups the elements of an observable sequence according to a specified key selector function and comparer. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// An equality comparer to compare keys with. + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// or or is null. + public static IObservable> GroupBy(this IObservable source, Func keySelector, IEqualityComparer comparer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.GroupBy(source, keySelector, comparer); + } + + /// + /// Groups the elements of an observable sequence and selects the resulting elements by using a specified function. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// The type of the elements within the groups computed for each element in the source sequence. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A function to map each source element to an element in an observable group. + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// or or is null. + public static IObservable> GroupBy(this IObservable source, Func keySelector, Func elementSelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (elementSelector == null) + { + throw new ArgumentNullException(nameof(elementSelector)); + } + + return s_impl.GroupBy(source, keySelector, elementSelector); + } + + /// + /// Groups the elements of an observable sequence according to a specified key selector function and comparer and selects the resulting elements by using a specified function. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// The type of the elements within the groups computed for each element in the source sequence. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A function to map each source element to an element in an observable group. + /// An equality comparer to compare keys with. + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// or or or is null. + public static IObservable> GroupBy(this IObservable source, Func keySelector, Func elementSelector, IEqualityComparer comparer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (elementSelector == null) + { + throw new ArgumentNullException(nameof(elementSelector)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.GroupBy(source, keySelector, elementSelector, comparer); + } + + /// + /// Groups the elements of an observable sequence with the specified initial capacity according to a specified key selector function. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// The initial number of elements that the underlying dictionary can contain. + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// or is null. + /// is less than 0. + public static IObservable> GroupBy(this IObservable source, Func keySelector, int capacity) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (capacity < 0) + { + throw new ArgumentOutOfRangeException(nameof(capacity)); + } + + return s_impl.GroupBy(source, keySelector, capacity); + } + + /// + /// Groups the elements of an observable sequence with the specified initial capacity according to a specified key selector function and comparer. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// The initial number of elements that the underlying dictionary can contain. + /// An equality comparer to compare keys with. + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// or or is null. + /// is less than 0. + public static IObservable> GroupBy(this IObservable source, Func keySelector, int capacity, IEqualityComparer comparer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (capacity < 0) + { + throw new ArgumentOutOfRangeException(nameof(capacity)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.GroupBy(source, keySelector, capacity, comparer); + } + + /// + /// Groups the elements of an observable sequence with the specified initial capacity and selects the resulting elements by using a specified function. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// The type of the elements within the groups computed for each element in the source sequence. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A function to map each source element to an element in an observable group. + /// The initial number of elements that the underlying dictionary can contain. + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// or or is null. + /// is less than 0. + public static IObservable> GroupBy(this IObservable source, Func keySelector, Func elementSelector, int capacity) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (elementSelector == null) + { + throw new ArgumentNullException(nameof(elementSelector)); + } + + if (capacity < 0) + { + throw new ArgumentOutOfRangeException(nameof(capacity)); + } + + return s_impl.GroupBy(source, keySelector, elementSelector, capacity); + } + + /// + /// Groups the elements of an observable sequence with the specified initial capacity according to a specified key selector function and comparer and selects the resulting elements by using a specified function. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// The type of the elements within the groups computed for each element in the source sequence. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A function to map each source element to an element in an observable group. + /// The initial number of elements that the underlying dictionary can contain. + /// An equality comparer to compare keys with. + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// or or or is null. + /// is less than 0. + public static IObservable> GroupBy(this IObservable source, Func keySelector, Func elementSelector, int capacity, IEqualityComparer comparer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (elementSelector == null) + { + throw new ArgumentNullException(nameof(elementSelector)); + } + + if (capacity < 0) + { + throw new ArgumentOutOfRangeException(nameof(capacity)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.GroupBy(source, keySelector, elementSelector, capacity, comparer); + } + + #endregion + + #region + GroupByUntil + + + /// + /// Groups the elements of an observable sequence according to a specified key selector function and comparer and selects the resulting elements by using a specified function. + /// A duration selector function is used to control the lifetime of groups. When a group expires, it receives an OnCompleted notification. When a new element with the same + /// key value as a reclaimed group occurs, the group will be reborn with a new lifetime request. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// The type of the elements within the groups computed for each element in the source sequence. + /// The type of the elements in the duration sequences obtained for each group to denote its lifetime. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A function to map each source element to an element in an observable group. + /// A function to signal the expiration of a group. + /// An equality comparer to compare keys with. + /// + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// If a group's lifetime expires, a new group with the same key value can be created once an element with such a key value is encountered. + /// + /// or or or or is null. + public static IObservable> GroupByUntil(this IObservable source, Func keySelector, Func elementSelector, Func, IObservable> durationSelector, IEqualityComparer comparer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (elementSelector == null) + { + throw new ArgumentNullException(nameof(elementSelector)); + } + + if (durationSelector == null) + { + throw new ArgumentNullException(nameof(durationSelector)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.GroupByUntil(source, keySelector, elementSelector, durationSelector, comparer); + } + + /// + /// Groups the elements of an observable sequence according to a specified key selector function and selects the resulting elements by using a specified function. + /// A duration selector function is used to control the lifetime of groups. When a group expires, it receives an OnCompleted notification. When a new element with the same + /// key value as a reclaimed group occurs, the group will be reborn with a new lifetime request. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// The type of the elements within the groups computed for each element in the source sequence. + /// The type of the elements in the duration sequences obtained for each group to denote its lifetime. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A function to map each source element to an element in an observable group. + /// A function to signal the expiration of a group. + /// + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// If a group's lifetime expires, a new group with the same key value can be created once an element with such a key value is encountered. + /// + /// or or or is null. + public static IObservable> GroupByUntil(this IObservable source, Func keySelector, Func elementSelector, Func, IObservable> durationSelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (elementSelector == null) + { + throw new ArgumentNullException(nameof(elementSelector)); + } + + if (durationSelector == null) + { + throw new ArgumentNullException(nameof(durationSelector)); + } + + return s_impl.GroupByUntil(source, keySelector, elementSelector, durationSelector); + } + + /// + /// Groups the elements of an observable sequence according to a specified key selector function and comparer. + /// A duration selector function is used to control the lifetime of groups. When a group expires, it receives an OnCompleted notification. When a new element with the same + /// key value as a reclaimed group occurs, the group will be reborn with a new lifetime request. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// The type of the elements in the duration sequences obtained for each group to denote its lifetime. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A function to signal the expiration of a group. + /// An equality comparer to compare keys with. + /// + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// If a group's lifetime expires, a new group with the same key value can be created once an element with such a key value is encountered. + /// + /// or or or is null. + public static IObservable> GroupByUntil(this IObservable source, Func keySelector, Func, IObservable> durationSelector, IEqualityComparer comparer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (durationSelector == null) + { + throw new ArgumentNullException(nameof(durationSelector)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.GroupByUntil(source, keySelector, durationSelector, comparer); + } + + /// + /// Groups the elements of an observable sequence according to a specified key selector function. + /// A duration selector function is used to control the lifetime of groups. When a group expires, it receives an OnCompleted notification. When a new element with the same + /// key value as a reclaimed group occurs, the group will be reborn with a new lifetime request. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// The type of the elements in the duration sequences obtained for each group to denote its lifetime. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A function to signal the expiration of a group. + /// + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// If a group's lifetime expires, a new group with the same key value can be created once an element with such a key value is encountered. + /// + /// or or is null. + public static IObservable> GroupByUntil(this IObservable source, Func keySelector, Func, IObservable> durationSelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (durationSelector == null) + { + throw new ArgumentNullException(nameof(durationSelector)); + } + + return s_impl.GroupByUntil(source, keySelector, durationSelector); + } + + /// + /// Groups the elements of an observable sequence with the specified initial capacity according to a specified key selector function and comparer and selects the resulting elements by using a specified function. + /// A duration selector function is used to control the lifetime of groups. When a group expires, it receives an OnCompleted notification. When a new element with the same + /// key value as a reclaimed group occurs, the group will be reborn with a new lifetime request. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// The type of the elements within the groups computed for each element in the source sequence. + /// The type of the elements in the duration sequences obtained for each group to denote its lifetime. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A function to map each source element to an element in an observable group. + /// A function to signal the expiration of a group. + /// The initial number of elements that the underlying dictionary can contain. + /// An equality comparer to compare keys with. + /// + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// If a group's lifetime expires, a new group with the same key value can be created once an element with such a key value is encountered. + /// + /// or or or or is null. + /// is less than 0. + public static IObservable> GroupByUntil(this IObservable source, Func keySelector, Func elementSelector, Func, IObservable> durationSelector, int capacity, IEqualityComparer comparer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (elementSelector == null) + { + throw new ArgumentNullException(nameof(elementSelector)); + } + + if (durationSelector == null) + { + throw new ArgumentNullException(nameof(durationSelector)); + } + + if (capacity < 0) + { + throw new ArgumentOutOfRangeException(nameof(capacity)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.GroupByUntil(source, keySelector, elementSelector, durationSelector, capacity, comparer); + } + + /// + /// Groups the elements of an observable sequence with the specified initial capacity according to a specified key selector function and selects the resulting elements by using a specified function. + /// A duration selector function is used to control the lifetime of groups. When a group expires, it receives an OnCompleted notification. When a new element with the same + /// key value as a reclaimed group occurs, the group will be reborn with a new lifetime request. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// The type of the elements within the groups computed for each element in the source sequence. + /// The type of the elements in the duration sequences obtained for each group to denote its lifetime. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A function to map each source element to an element in an observable group. + /// A function to signal the expiration of a group. + /// The initial number of elements that the underlying dictionary can contain. + /// + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// If a group's lifetime expires, a new group with the same key value can be created once an element with such a key value is encountered. + /// + /// or or or is null. + /// is less than 0. + public static IObservable> GroupByUntil(this IObservable source, Func keySelector, Func elementSelector, Func, IObservable> durationSelector, int capacity) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (elementSelector == null) + { + throw new ArgumentNullException(nameof(elementSelector)); + } + + if (durationSelector == null) + { + throw new ArgumentNullException(nameof(durationSelector)); + } + + if (capacity < 0) + { + throw new ArgumentOutOfRangeException(nameof(capacity)); + } + + return s_impl.GroupByUntil(source, keySelector, elementSelector, durationSelector, capacity); + } + + /// + /// Groups the elements of an observable sequence with the specified initial capacity according to a specified key selector function and comparer. + /// A duration selector function is used to control the lifetime of groups. When a group expires, it receives an OnCompleted notification. When a new element with the same + /// key value as a reclaimed group occurs, the group will be reborn with a new lifetime request. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// The type of the elements in the duration sequences obtained for each group to denote its lifetime. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A function to signal the expiration of a group. + /// The initial number of elements that the underlying dictionary can contain. + /// An equality comparer to compare keys with. + /// + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// If a group's lifetime expires, a new group with the same key value can be created once an element with such a key value is encountered. + /// + /// or or or is null. + /// is less than 0. + public static IObservable> GroupByUntil(this IObservable source, Func keySelector, Func, IObservable> durationSelector, int capacity, IEqualityComparer comparer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (durationSelector == null) + { + throw new ArgumentNullException(nameof(durationSelector)); + } + + if (capacity < 0) + { + throw new ArgumentOutOfRangeException(nameof(capacity)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + return s_impl.GroupByUntil(source, keySelector, durationSelector, capacity, comparer); + } + + /// + /// Groups the elements of an observable sequence with the specified initial capacity according to a specified key selector function. + /// A duration selector function is used to control the lifetime of groups. When a group expires, it receives an OnCompleted notification. When a new element with the same + /// key value as a reclaimed group occurs, the group will be reborn with a new lifetime request. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// The type of the elements in the duration sequences obtained for each group to denote its lifetime. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A function to signal the expiration of a group. + /// The initial number of elements that the underlying dictionary can contain. + /// + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// If a group's lifetime expires, a new group with the same key value can be created once an element with such a key value is encountered. + /// + /// or or is null. + /// is less than 0. + public static IObservable> GroupByUntil(this IObservable source, Func keySelector, Func, IObservable> durationSelector, int capacity) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector == null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (durationSelector == null) + { + throw new ArgumentNullException(nameof(durationSelector)); + } + + if (capacity < 0) + { + throw new ArgumentOutOfRangeException(nameof(capacity)); + } + + return s_impl.GroupByUntil(source, keySelector, durationSelector, capacity); + } + + #endregion + + #region + GroupJoin + + + /// + /// Correlates the elements of two sequences based on overlapping durations, and groups the results. + /// + /// The type of the elements in the left source sequence. + /// The type of the elements in the right source sequence. + /// The type of the elements in the duration sequence denoting the computed duration of each element in the left source sequence. + /// The type of the elements in the duration sequence denoting the computed duration of each element in the right source sequence. + /// The type of the elements in the result sequence, obtained by invoking the result selector function for source elements with overlapping duration. + /// The left observable sequence to join elements for. + /// The right observable sequence to join elements for. + /// A function to select the duration of each element of the left observable sequence, used to determine overlap. + /// A function to select the duration of each element of the right observable sequence, used to determine overlap. + /// A function invoked to compute a result element for any element of the left sequence with overlapping elements from the right observable sequence. + /// An observable sequence that contains result elements computed from source elements that have an overlapping duration. + /// or or or or is null. + public static IObservable GroupJoin(this IObservable left, IObservable right, Func> leftDurationSelector, Func> rightDurationSelector, Func, TResult> resultSelector) + { + if (left == null) + { + throw new ArgumentNullException(nameof(left)); + } + + if (right == null) + { + throw new ArgumentNullException(nameof(right)); + } + + if (leftDurationSelector == null) + { + throw new ArgumentNullException(nameof(leftDurationSelector)); + } + + if (rightDurationSelector == null) + { + throw new ArgumentNullException(nameof(rightDurationSelector)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.GroupJoin(left, right, leftDurationSelector, rightDurationSelector, resultSelector); + } + + #endregion + + #region + Join + + + /// + /// Correlates the elements of two sequences based on overlapping durations. + /// + /// The type of the elements in the left source sequence. + /// The type of the elements in the right source sequence. + /// The type of the elements in the duration sequence denoting the computed duration of each element in the left source sequence. + /// The type of the elements in the duration sequence denoting the computed duration of each element in the right source sequence. + /// The type of the elements in the result sequence, obtained by invoking the result selector function for source elements with overlapping duration. + /// The left observable sequence to join elements for. + /// The right observable sequence to join elements for. + /// A function to select the duration of each element of the left observable sequence, used to determine overlap. + /// A function to select the duration of each element of the right observable sequence, used to determine overlap. + /// A function invoked to compute a result element for any two overlapping elements of the left and right observable sequences. + /// An observable sequence that contains result elements computed from source elements that have an overlapping duration. + /// or or or or is null. + public static IObservable Join(this IObservable left, IObservable right, Func> leftDurationSelector, Func> rightDurationSelector, Func resultSelector) + { + if (left == null) + { + throw new ArgumentNullException(nameof(left)); + } + + if (right == null) + { + throw new ArgumentNullException(nameof(right)); + } + + if (leftDurationSelector == null) + { + throw new ArgumentNullException(nameof(leftDurationSelector)); + } + + if (rightDurationSelector == null) + { + throw new ArgumentNullException(nameof(rightDurationSelector)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.Join(left, right, leftDurationSelector, rightDurationSelector, resultSelector); + } + + #endregion + + #region + OfType + + + /// + /// Filters the elements of an observable sequence based on the specified type. + /// + /// The type to filter the elements in the source sequence on. + /// The observable sequence that contains the elements to be filtered. + /// An observable sequence that contains elements from the input sequence of type TResult. + /// is null. + public static IObservable OfType(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.OfType(source); + } + + #endregion + + #region + Select + + + /// + /// Projects each element of an observable sequence into a new form. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence, obtained by running the selector function for each element in the source sequence. + /// A sequence of elements to invoke a transform function on. + /// A transform function to apply to each source element. + /// An observable sequence whose elements are the result of invoking the transform function on each element of source. + /// or is null. + public static IObservable Select(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Select(source, selector); + } + + /// + /// Projects each element of an observable sequence into a new form by incorporating the element's index. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence, obtained by running the selector function for each element in the source sequence. + /// A sequence of elements to invoke a transform function on. + /// A transform function to apply to each source element; the second parameter of the function represents the index of the source element. + /// An observable sequence whose elements are the result of invoking the transform function on each element of source. + /// or is null. + public static IObservable Select(this IObservable source, Func selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Select(source, selector); + } + + #endregion + + #region + SelectMany + + + /// + /// Projects each element of the source observable sequence to the other observable sequence and merges the resulting observable sequences into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the other sequence and the elements in the result sequence. + /// An observable sequence of elements to project. + /// An observable sequence to project each element from the source sequence onto. + /// An observable sequence whose elements are the result of projecting each source element onto the other sequence and merging all the resulting sequences together. + /// or is null. + public static IObservable SelectMany(this IObservable source, IObservable other) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return s_impl.SelectMany(source, other); + } + + /// + /// Projects each element of an observable sequence to an observable sequence and merges the resulting observable sequences into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the projected inner sequences and the elements in the merged result sequence. + /// An observable sequence of elements to project. + /// A transform function to apply to each element. + /// An observable sequence whose elements are the result of invoking the one-to-many transform function on each element of the input sequence. + /// or is null. + public static IObservable SelectMany(this IObservable source, Func> selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.SelectMany(source, selector); + } + + /// + /// Projects each element of an observable sequence to an observable sequence by incorporating the element's index and merges the resulting observable sequences into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the projected inner sequences and the elements in the merged result sequence. + /// An observable sequence of elements to project. + /// A transform function to apply to each element; the second parameter of the function represents the index of the source element. + /// An observable sequence whose elements are the result of invoking the one-to-many transform function on each element of the input sequence. + /// or is null. + public static IObservable SelectMany(this IObservable source, Func> selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.SelectMany(source, selector); + } + + /// + /// Projects each element of an observable sequence to a task and merges all of the task results into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the result produced by the projected tasks and the elements in the merged result sequence. + /// An observable sequence of elements to project. + /// A transform function to apply to each element. + /// An observable sequence whose elements are the result of the tasks executed for each element of the input sequence. + /// This overload supports composition of observable sequences and tasks, without requiring manual conversion of the tasks to observable sequences using . + /// or is null. + public static IObservable SelectMany(this IObservable source, Func> selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.SelectMany(source, selector); + } + + /// + /// Projects each element of an observable sequence to a task by incorporating the element's index and merges all of the task results into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the result produced by the projected tasks and the elements in the merged result sequence. + /// An observable sequence of elements to project. + /// A transform function to apply to each element; the second parameter of the function represents the index of the source element. + /// An observable sequence whose elements are the result of the tasks executed for each element of the input sequence. + /// This overload supports composition of observable sequences and tasks, without requiring manual conversion of the tasks to observable sequences using . + /// or is null. + public static IObservable SelectMany(this IObservable source, Func> selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.SelectMany(source, selector); + } + + /// + /// Projects each element of an observable sequence to a task with cancellation support and merges all of the task results into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the result produced by the projected tasks and the elements in the merged result sequence. + /// An observable sequence of elements to project. + /// A transform function to apply to each element. + /// An observable sequence whose elements are the result of the tasks executed for each element of the input sequence. + /// This overload supports composition of observable sequences and tasks, without requiring manual conversion of the tasks to observable sequences using . + /// or is null. + public static IObservable SelectMany(this IObservable source, Func> selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.SelectMany(source, selector); + } + + /// + /// Projects each element of an observable sequence to a task by incorporating the element's index with cancellation support and merges all of the task results into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the result produced by the projected tasks and the elements in the merged result sequence. + /// An observable sequence of elements to project. + /// A transform function to apply to each element; the second parameter of the function represents the index of the source element. + /// An observable sequence whose elements are the result of the tasks executed for each element of the input sequence. + /// This overload supports composition of observable sequences and tasks, without requiring manual conversion of the tasks to observable sequences using . + /// or is null. + public static IObservable SelectMany(this IObservable source, Func> selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.SelectMany(source, selector); + } + + /// + /// Projects each element of an observable sequence to an observable sequence, invokes the result selector for the source element and each of the corresponding inner sequence's elements, and merges the results into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the projected intermediate sequences. + /// The type of the elements in the result sequence, obtained by using the selector to combine source sequence elements with their corresponding intermediate sequence elements. + /// An observable sequence of elements to project. + /// A transform function to apply to each element. + /// A transform function to apply to each element of the intermediate sequence. + /// An observable sequence whose elements are the result of invoking the one-to-many transform function collectionSelector on each element of the input sequence and then mapping each of those sequence elements and their corresponding source element to a result element. + /// or or is null. + public static IObservable SelectMany(this IObservable source, Func> collectionSelector, Func resultSelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (collectionSelector == null) + { + throw new ArgumentNullException(nameof(collectionSelector)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.SelectMany(source, collectionSelector, resultSelector); + } + + /// + /// Projects each element of an observable sequence to an observable sequence by incorporating the element's index, invokes the result selector for the source element and each of the corresponding inner sequence's elements, and merges the results into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the projected intermediate sequences. + /// The type of the elements in the result sequence, obtained by using the selector to combine source sequence elements with their corresponding intermediate sequence elements. + /// An observable sequence of elements to project. + /// A transform function to apply to each element; the second parameter of the function represents the index of the source element. + /// A transform function to apply to each element of the intermediate sequence; the second parameter of the function represents the index of the source element and the fourth parameter represents the index of the intermediate element. + /// An observable sequence whose elements are the result of invoking the one-to-many transform function collectionSelector on each element of the input sequence and then mapping each of those sequence elements and their corresponding source element to a result element. + /// or or is null. + public static IObservable SelectMany(this IObservable source, Func> collectionSelector, Func resultSelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (collectionSelector == null) + { + throw new ArgumentNullException(nameof(collectionSelector)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.SelectMany(source, collectionSelector, resultSelector); + } + + /// + /// Projects each element of an observable sequence to a task, invokes the result selector for the source element and the task result, and merges the results into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the results produced by the projected intermediate tasks. + /// The type of the elements in the result sequence, obtained by using the selector to combine source sequence elements with their corresponding intermediate task results. + /// An observable sequence of elements to project. + /// A transform function to apply to each element. + /// A transform function to apply to each element of the intermediate sequence. + /// An observable sequence whose elements are the result of obtaining a task for each element of the input sequence and then mapping the task's result and its corresponding source element to a result element. + /// or or is null. + /// This overload supports using LINQ query comprehension syntax in C# and Visual Basic to compose observable sequences and tasks, without requiring manual conversion of the tasks to observable sequences using . + public static IObservable SelectMany(this IObservable source, Func> taskSelector, Func resultSelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (taskSelector == null) + { + throw new ArgumentNullException(nameof(taskSelector)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.SelectMany(source, taskSelector, resultSelector); + } + + /// + /// Projects each element of an observable sequence to a task by incorporating the element's index, invokes the result selector for the source element and the task result, and merges the results into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the results produced by the projected intermediate tasks. + /// The type of the elements in the result sequence, obtained by using the selector to combine source sequence elements with their corresponding intermediate task results. + /// An observable sequence of elements to project. + /// A transform function to apply to each element; the second parameter of the function represents the index of the source element. + /// A transform function to apply to each element of the intermediate sequence; the second parameter of the function represents the index of the source element. + /// An observable sequence whose elements are the result of obtaining a task for each element of the input sequence and then mapping the task's result and its corresponding source element to a result element. + /// or or is null. + /// This overload supports using LINQ query comprehension syntax in C# and Visual Basic to compose observable sequences and tasks, without requiring manual conversion of the tasks to observable sequences using . + public static IObservable SelectMany(this IObservable source, Func> taskSelector, Func resultSelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (taskSelector == null) + { + throw new ArgumentNullException(nameof(taskSelector)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.SelectMany(source, taskSelector, resultSelector); + } + + /// + /// Projects each element of an observable sequence to a task with cancellation support, invokes the result selector for the source element and the task result, and merges the results into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the results produced by the projected intermediate tasks. + /// The type of the elements in the result sequence, obtained by using the selector to combine source sequence elements with their corresponding intermediate task results. + /// An observable sequence of elements to project. + /// A transform function to apply to each element. + /// A transform function to apply to each element of the intermediate sequence. + /// An observable sequence whose elements are the result of obtaining a task for each element of the input sequence and then mapping the task's result and its corresponding source element to a result element. + /// or or is null. + /// This overload supports using LINQ query comprehension syntax in C# and Visual Basic to compose observable sequences and tasks, without requiring manual conversion of the tasks to observable sequences using . + public static IObservable SelectMany(this IObservable source, Func> taskSelector, Func resultSelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (taskSelector == null) + { + throw new ArgumentNullException(nameof(taskSelector)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.SelectMany(source, taskSelector, resultSelector); + } + + /// + /// Projects each element of an observable sequence to a task by incorporating the element's index with cancellation support, invokes the result selector for the source element and the task result, and merges the results into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the results produced by the projected intermediate tasks. + /// The type of the elements in the result sequence, obtained by using the selector to combine source sequence elements with their corresponding intermediate task results. + /// An observable sequence of elements to project. + /// A transform function to apply to each element; the second parameter of the function represents the index of the source element. + /// A transform function to apply to each element of the intermediate sequence; the second parameter of the function represents the index of the source element. + /// An observable sequence whose elements are the result of obtaining a task for each element of the input sequence and then mapping the task's result and its corresponding source element to a result element. + /// or or is null. + /// This overload supports using LINQ query comprehension syntax in C# and Visual Basic to compose observable sequences and tasks, without requiring manual conversion of the tasks to observable sequences using . + public static IObservable SelectMany(this IObservable source, Func> taskSelector, Func resultSelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (taskSelector == null) + { + throw new ArgumentNullException(nameof(taskSelector)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.SelectMany(source, taskSelector, resultSelector); + } + + /// + /// Projects each notification of an observable sequence to an observable sequence and merges the resulting observable sequences into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the projected inner sequences and the elements in the merged result sequence. + /// An observable sequence of notifications to project. + /// A transform function to apply to each element. + /// A transform function to apply when an error occurs in the source sequence. + /// A transform function to apply when the end of the source sequence is reached. + /// An observable sequence whose elements are the result of invoking the one-to-many transform function corresponding to each notification in the input sequence. + /// or or or is null. + public static IObservable SelectMany(this IObservable source, Func> onNext, Func> onError, Func> onCompleted) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + if (onError == null) + { + throw new ArgumentNullException(nameof(onError)); + } + + if (onCompleted == null) + { + throw new ArgumentNullException(nameof(onCompleted)); + } + + return s_impl.SelectMany(source, onNext, onError, onCompleted); + } + + /// + /// Projects each notification of an observable sequence to an observable sequence by incorporating the element's index and merges the resulting observable sequences into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the projected inner sequences and the elements in the merged result sequence. + /// An observable sequence of notifications to project. + /// A transform function to apply to each element; the second parameter of the function represents the index of the source element. + /// A transform function to apply when an error occurs in the source sequence. + /// A transform function to apply when the end of the source sequence is reached. + /// An observable sequence whose elements are the result of invoking the one-to-many transform function corresponding to each notification in the input sequence. + /// or or or is null. + public static IObservable SelectMany(this IObservable source, Func> onNext, Func> onError, Func> onCompleted) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + if (onError == null) + { + throw new ArgumentNullException(nameof(onError)); + } + + if (onCompleted == null) + { + throw new ArgumentNullException(nameof(onCompleted)); + } + + return s_impl.SelectMany(source, onNext, onError, onCompleted); + } + + /// + /// Projects each element of an observable sequence to an enumerable sequence and concatenates the resulting enumerable sequences into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the projected inner enumerable sequences and the elements in the merged result sequence. + /// An observable sequence of elements to project. + /// A transform function to apply to each element. + /// An observable sequence whose elements are the result of invoking the one-to-many transform function on each element of the input sequence. + /// or is null. + /// The projected sequences are enumerated synchronously within the OnNext call of the source sequence. In order to do a concurrent, non-blocking merge, change the selector to return an observable sequence obtained using the conversion. + public static IObservable SelectMany(this IObservable source, Func> selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.SelectMany(source, selector); + } + + /// + /// Projects each element of an observable sequence to an enumerable sequence by incorporating the element's index and concatenates the resulting enumerable sequences into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the projected inner enumerable sequences and the elements in the merged result sequence. + /// An observable sequence of elements to project. + /// A transform function to apply to each element; the second parameter of the function represents the index of the source element. + /// An observable sequence whose elements are the result of invoking the one-to-many transform function on each element of the input sequence. + /// or is null. + /// The projected sequences are enumerated synchronously within the OnNext call of the source sequence. In order to do a concurrent, non-blocking merge, change the selector to return an observable sequence obtained using the conversion. + public static IObservable SelectMany(this IObservable source, Func> selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.SelectMany(source, selector); + } + + /// + /// Projects each element of an observable sequence to an enumerable sequence, invokes the result selector for the source element and each of the corresponding inner sequence's elements, and merges the results into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the projected intermediate enumerable sequences. + /// The type of the elements in the result sequence, obtained by using the selector to combine source sequence elements with their corresponding intermediate sequence elements. + /// An observable sequence of elements to project. + /// A transform function to apply to each element. + /// A transform function to apply to each element of the intermediate sequence. + /// An observable sequence whose elements are the result of invoking the one-to-many transform function collectionSelector on each element of the input sequence and then mapping each of those sequence elements and their corresponding source element to a result element. + /// or or is null. + /// The projected sequences are enumerated synchronously within the OnNext call of the source sequence. In order to do a concurrent, non-blocking merge, change the selector to return an observable sequence obtained using the conversion. + public static IObservable SelectMany(this IObservable source, Func> collectionSelector, Func resultSelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (collectionSelector == null) + { + throw new ArgumentNullException(nameof(collectionSelector)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.SelectMany(source, collectionSelector, resultSelector); + } + + /// + /// Projects each element of an observable sequence to an enumerable sequence by incorporating the element's index, invokes the result selector for the source element and each of the corresponding inner sequence's elements, and merges the results into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the projected intermediate enumerable sequences. + /// The type of the elements in the result sequence, obtained by using the selector to combine source sequence elements with their corresponding intermediate sequence elements. + /// An observable sequence of elements to project. + /// A transform function to apply to each element; the second parameter of the function represents the index of the source element. + /// A transform function to apply to each element of the intermediate sequence; the second parameter of the function represents the index of the source element and the fourth parameter represents the index of the intermediate element. + /// An observable sequence whose elements are the result of invoking the one-to-many transform function collectionSelector on each element of the input sequence and then mapping each of those sequence elements and their corresponding source element to a result element. + /// or or is null. + /// The projected sequences are enumerated synchronously within the OnNext call of the source sequence. In order to do a concurrent, non-blocking merge, change the selector to return an observable sequence obtained using the conversion. + public static IObservable SelectMany(this IObservable source, Func> collectionSelector, Func resultSelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (collectionSelector == null) + { + throw new ArgumentNullException(nameof(collectionSelector)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.SelectMany(source, collectionSelector, resultSelector); + } + + #endregion + + #region + Skip + + + /// + /// Bypasses a specified number of elements in an observable sequence and then returns the remaining elements. + /// + /// The type of the elements in the source sequence. + /// The sequence to take elements from. + /// The number of elements to skip before returning the remaining elements. + /// An observable sequence that contains the elements that occur after the specified index in the input sequence. + /// is null. + /// is less than zero. + public static IObservable Skip(this IObservable source, int count) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + return s_impl.Skip(source, count); + } + + #endregion + + #region + SkipWhile + + + /// + /// Bypasses elements in an observable sequence as long as a specified condition is true and then returns the remaining elements. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to return elements from. + /// A function to test each element for a condition. + /// An observable sequence that contains the elements from the input sequence starting at the first element in the linear series that does not pass the test specified by predicate. + /// or is null. + public static IObservable SkipWhile(this IObservable source, Func predicate) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return s_impl.SkipWhile(source, predicate); + } + + /// + /// Bypasses elements in an observable sequence as long as a specified condition is true and then returns the remaining elements. + /// The element's index is used in the logic of the predicate function. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to return elements from. + /// A function to test each element for a condition; the second parameter of the function represents the index of the source element. + /// An observable sequence that contains the elements from the input sequence starting at the first element in the linear series that does not pass the test specified by predicate. + /// or is null. + public static IObservable SkipWhile(this IObservable source, Func predicate) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return s_impl.SkipWhile(source, predicate); + } + + #endregion + + #region + Take + + + /// + /// Returns a specified number of contiguous elements from the start of an observable sequence. + /// + /// The type of the elements in the source sequence. + /// The sequence to take elements from. + /// The number of elements to return. + /// An observable sequence that contains the specified number of elements from the start of the input sequence. + /// is null. + /// is less than zero. + public static IObservable Take(this IObservable source, int count) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + return s_impl.Take(source, count); + } + + /// + /// Returns a specified number of contiguous elements from the start of an observable sequence, using the specified scheduler for the edge case of Take(0). + /// + /// The type of the elements in the source sequence. + /// The sequence to take elements from. + /// The number of elements to return. + /// Scheduler used to produce an OnCompleted message in case count is set to 0. + /// An observable sequence that contains the specified number of elements from the start of the input sequence. + /// or is null. + /// is less than zero. + public static IObservable Take(this IObservable source, int count, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Take(source, count, scheduler); + } + + #endregion + + #region + TakeWhile + + + /// + /// Returns elements from an observable sequence as long as a specified condition is true. + /// + /// The type of the elements in the source sequence. + /// A sequence to return elements from. + /// A function to test each element for a condition. + /// An observable sequence that contains the elements from the input sequence that occur before the element at which the test no longer passes. + /// or is null. + public static IObservable TakeWhile(this IObservable source, Func predicate) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return s_impl.TakeWhile(source, predicate); + } + + /// + /// Returns elements from an observable sequence as long as a specified condition is true. + /// The element's index is used in the logic of the predicate function. + /// + /// The type of the elements in the source sequence. + /// A sequence to return elements from. + /// A function to test each element for a condition; the second parameter of the function represents the index of the source element. + /// An observable sequence that contains the elements from the input sequence that occur before the element at which the test no longer passes. + /// or is null. + public static IObservable TakeWhile(this IObservable source, Func predicate) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return s_impl.TakeWhile(source, predicate); + } + + #endregion + + #region + Where + + + /// + /// Filters the elements of an observable sequence based on a predicate. + /// + /// The type of the elements in the source sequence. + /// An observable sequence whose elements to filter. + /// A function to test each source element for a condition. + /// An observable sequence that contains elements from the input sequence that satisfy the condition. + /// or is null. + public static IObservable Where(this IObservable source, Func predicate) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return s_impl.Where(source, predicate); + } + + /// + /// Filters the elements of an observable sequence based on a predicate by incorporating the element's index. + /// + /// The type of the elements in the source sequence. + /// An observable sequence whose elements to filter. + /// A function to test each source element for a condition; the second parameter of the function represents the index of the source element. + /// An observable sequence that contains elements from the input sequence that satisfy the condition. + /// or is null. + public static IObservable Where(this IObservable source, Func predicate) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return s_impl.Where(source, predicate); + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable.Time.cs b/LibExternal/System.Reactive/Linq/Observable.Time.cs new file mode 100644 index 0000000..8a8dae0 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable.Time.cs @@ -0,0 +1,2441 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Concurrency; + +namespace System.Reactive.Linq +{ + public static partial class Observable + { + #region + Buffer + + + #region TimeSpan only + + /// + /// Projects each element of an observable sequence into consecutive non-overlapping buffers which are produced based on timing information. + /// + /// The type of the elements in the source sequence, and in the lists in the result sequence. + /// Source sequence to produce buffers over. + /// Length of each buffer. + /// An observable sequence of buffers. + /// is null. + /// is less than TimeSpan.Zero. + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create buffers as fast as it can. + /// Because all source sequence elements end up in one of the buffers, some buffers won't have a zero time span. This is a side-effect of the asynchrony introduced + /// by the scheduler, where the action to close the current buffer and to create a new buffer may not execute immediately, despite the TimeSpan.Zero due time. + /// + public static IObservable> Buffer(this IObservable source, TimeSpan timeSpan) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (timeSpan < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(timeSpan)); + } + + return s_impl.Buffer(source, timeSpan); + } + + /// + /// Projects each element of an observable sequence into consecutive non-overlapping buffers which are produced based on timing information, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence, and in the lists in the result sequence. + /// Source sequence to produce buffers over. + /// Length of each buffer. + /// Scheduler to run buffering timers on. + /// An observable sequence of buffers. + /// or is null. + /// is less than TimeSpan.Zero. + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create buffers as fast as it can. + /// Because all source sequence elements end up in one of the buffers, some buffers won't have a zero time span. This is a side-effect of the asynchrony introduced + /// by the scheduler, where the action to close the current buffer and to create a new buffer may not execute immediately, despite the TimeSpan.Zero due time. + /// + public static IObservable> Buffer(this IObservable source, TimeSpan timeSpan, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (timeSpan < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(timeSpan)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Buffer(source, timeSpan, scheduler); + } + + /// + /// Projects each element of an observable sequence into zero or more buffers which are produced based on timing information. + /// + /// The type of the elements in the source sequence, and in the lists in the result sequence. + /// Source sequence to produce buffers over. + /// Length of each buffer. + /// Interval between creation of consecutive buffers. + /// An observable sequence of buffers. + /// is null. + /// or is less than TimeSpan.Zero. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create buffers with minimum duration + /// length. However, some buffers won't have a zero time span. This is a side-effect of the asynchrony introduced by the scheduler, where the action to close the + /// current buffer may not execute immediately, despite the TimeSpan.Zero due time. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create buffers as fast as it can. + /// However, this doesn't mean all buffers will start at the beginning of the source sequence. This is a side-effect of the asynchrony introduced by the scheduler, + /// where the action to create a new buffer may not execute immediately, despite the TimeSpan.Zero due time. + /// + /// + public static IObservable> Buffer(this IObservable source, TimeSpan timeSpan, TimeSpan timeShift) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (timeSpan < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(timeSpan)); + } + + if (timeShift < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(timeShift)); + } + + return s_impl.Buffer(source, timeSpan, timeShift); + } + + /// + /// Projects each element of an observable sequence into zero or more buffers which are produced based on timing information, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence, and in the lists in the result sequence. + /// Source sequence to produce buffers over. + /// Length of each buffer. + /// Interval between creation of consecutive buffers. + /// Scheduler to run buffering timers on. + /// An observable sequence of buffers. + /// or is null. + /// or is less than TimeSpan.Zero. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create buffers with minimum duration + /// length. However, some buffers won't have a zero time span. This is a side-effect of the asynchrony introduced by the scheduler, where the action to close the + /// current buffer may not execute immediately, despite the TimeSpan.Zero due time. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create buffers as fast as it can. + /// However, this doesn't mean all buffers will start at the beginning of the source sequence. This is a side-effect of the asynchrony introduced by the scheduler, + /// where the action to create a new buffer may not execute immediately, despite the TimeSpan.Zero due time. + /// + /// + public static IObservable> Buffer(this IObservable source, TimeSpan timeSpan, TimeSpan timeShift, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (timeSpan < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(timeSpan)); + } + + if (timeShift < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(timeShift)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Buffer(source, timeSpan, timeShift, scheduler); + } + + #endregion + + #region TimeSpan + int + + /// + /// Projects each element of an observable sequence into a buffer that's sent out when either it's full or a given amount of time has elapsed. + /// A useful real-world analogy of this overload is the behavior of a ferry leaving the dock when all seats are taken, or at the scheduled time of departure, whichever event occurs first. + /// + /// The type of the elements in the source sequence, and in the lists in the result sequence. + /// Source sequence to produce buffers over. + /// Maximum time length of a window. + /// Maximum element count of a window. + /// An observable sequence of buffers. + /// is null. + /// is less than TimeSpan.Zero. -or- is less than or equal to zero. + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create buffers as fast as it can. + /// Because all source sequence elements end up in one of the buffers, some buffers won't have a zero time span. This is a side-effect of the asynchrony introduced + /// by the scheduler, where the action to close the current buffer and to create a new buffer may not execute immediately, despite the TimeSpan.Zero due time. + /// + public static IObservable> Buffer(this IObservable source, TimeSpan timeSpan, int count) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (timeSpan < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(timeSpan)); + } + + if (count <= 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + return s_impl.Buffer(source, timeSpan, count); + } + + /// + /// Projects each element of an observable sequence into a buffer that's sent out when either it's full or a given amount of time has elapsed, using the specified scheduler to run timers. + /// A useful real-world analogy of this overload is the behavior of a ferry leaving the dock when all seats are taken, or at the scheduled time of departure, whichever event occurs first. + /// + /// The type of the elements in the source sequence, and in the lists in the result sequence. + /// Source sequence to produce buffers over. + /// Maximum time length of a buffer. + /// Maximum element count of a buffer. + /// Scheduler to run buffering timers on. + /// An observable sequence of buffers. + /// or is null. + /// is less than TimeSpan.Zero. -or- is less than or equal to zero. + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create buffers as fast as it can. + /// Because all source sequence elements end up in one of the buffers, some buffers won't have a zero time span. This is a side-effect of the asynchrony introduced + /// by the scheduler, where the action to close the current buffer and to create a new buffer may not execute immediately, despite the TimeSpan.Zero due time. + /// + public static IObservable> Buffer(this IObservable source, TimeSpan timeSpan, int count, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (timeSpan < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(timeSpan)); + } + + if (count <= 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Buffer(source, timeSpan, count, scheduler); + } + + #endregion + + #endregion + + #region + Delay + + + #region TimeSpan + + /// + /// Time shifts the observable sequence by the specified relative time duration. + /// The relative time intervals between the values are preserved. + /// + /// The type of the elements in the source sequence. + /// Source sequence to delay values for. + /// Relative time by which to shift the observable sequence. If this value is equal to TimeSpan.Zero, the scheduler will dispatch observer callbacks as soon as possible. + /// Time-shifted sequence. + /// is null. + /// is less than TimeSpan.Zero. + /// + /// + /// This operator is less efficient than DelaySubscription because it records all notifications and time-delays those. This allows for immediate propagation of errors. + /// + /// + /// Observer callbacks for the resulting sequence will be run on the default scheduler. This effect is similar to using ObserveOn. + /// + /// + /// Exceptions signaled by the source sequence through an OnError callback are forwarded immediately to the result sequence. Any OnNext notifications that were in the queue at the point of the OnError callback will be dropped. + /// In order to delay error propagation, consider using the Observable.Materialize and Observable.Dematerialize operators, or use DelaySubscription. + /// + /// + public static IObservable Delay(this IObservable source, TimeSpan dueTime) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (dueTime < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(dueTime)); + } + + return s_impl.Delay(source, dueTime); + } + + /// + /// Time shifts the observable sequence by the specified relative time duration, using the specified scheduler to run timers. + /// The relative time intervals between the values are preserved. + /// + /// The type of the elements in the source sequence. + /// Source sequence to delay values for. + /// Relative time by which to shift the observable sequence. If this value is equal to TimeSpan.Zero, the scheduler will dispatch observer callbacks as soon as possible. + /// Scheduler to run the delay timers on. + /// Time-shifted sequence. + /// or is null. + /// is less than TimeSpan.Zero. + /// + /// + /// This operator is less efficient than DelaySubscription because it records all notifications and time-delays those. This allows for immediate propagation of errors. + /// + /// + /// Observer callbacks for the resulting sequence will be run on the specified scheduler. This effect is similar to using ObserveOn. + /// + /// + /// Exceptions signaled by the source sequence through an OnError callback are forwarded immediately to the result sequence. Any OnNext notifications that were in the queue at the point of the OnError callback will be dropped. + /// + /// + /// Exceptions signaled by the source sequence through an OnError callback are forwarded immediately to the result sequence. Any OnNext notifications that were in the queue at the point of the OnError callback will be dropped. + /// In order to delay error propagation, consider using the Observable.Materialize and Observable.Dematerialize operators, or use DelaySubscription. + /// + /// + public static IObservable Delay(this IObservable source, TimeSpan dueTime, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (dueTime < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(dueTime)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Delay(source, dueTime, scheduler); + } + + #endregion + + #region DateTimeOffset + + /// + /// Time shifts the observable sequence to start propagating notifications at the specified absolute time. + /// The relative time intervals between the values are preserved. + /// + /// The type of the elements in the source sequence. + /// Source sequence to delay values for. + /// Absolute time used to shift the observable sequence; the relative time shift gets computed upon subscription. If this value is less than or equal to DateTimeOffset.UtcNow, the scheduler will dispatch observer callbacks as soon as possible. + /// Time-shifted sequence. + /// is null. + /// + /// + /// This operator is less efficient than DelaySubscription because it records all notifications and time-delays those. This allows for immediate propagation of errors. + /// + /// + /// Observer callbacks for the resulting sequence will be run on the default scheduler. This effect is similar to using ObserveOn. + /// + /// + /// Exceptions signaled by the source sequence through an OnError callback are forwarded immediately to the result sequence. Any OnNext notifications that were in the queue at the point of the OnError callback will be dropped. + /// In order to delay error propagation, consider using the Observable.Materialize and Observable.Dematerialize operators, or use DelaySubscription. + /// + /// + public static IObservable Delay(this IObservable source, DateTimeOffset dueTime) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Delay(source, dueTime); + } + + /// + /// Time shifts the observable sequence to start propagating notifications at the specified absolute time, using the specified scheduler to run timers. + /// The relative time intervals between the values are preserved. + /// + /// The type of the elements in the source sequence. + /// Source sequence to delay values for. + /// Absolute time used to shift the observable sequence; the relative time shift gets computed upon subscription. If this value is less than or equal to DateTimeOffset.UtcNow, the scheduler will dispatch observer callbacks as soon as possible. + /// Scheduler to run the delay timers on. + /// Time-shifted sequence. + /// or is null. + /// + /// + /// This operator is less efficient than DelaySubscription because it records all notifications and time-delays those. This allows for immediate propagation of errors. + /// + /// + /// Observer callbacks for the resulting sequence will be run on the specified scheduler. This effect is similar to using ObserveOn. + /// + /// + /// Exceptions signaled by the source sequence through an OnError callback are forwarded immediately to the result sequence. Any OnNext notifications that were in the queue at the point of the OnError callback will be dropped. + /// In order to delay error propagation, consider using the Observable.Materialize and Observable.Dematerialize operators, or use DelaySubscription. + /// + /// + public static IObservable Delay(this IObservable source, DateTimeOffset dueTime, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Delay(source, dueTime, scheduler); + } + + #endregion + + #region Duration selector + + /// + /// Time shifts the observable sequence based on a delay selector function for each element. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the delay sequences used to denote the delay duration of each element in the source sequence. + /// Source sequence to delay values for. + /// Selector function to retrieve a sequence indicating the delay for each given element. + /// Time-shifted sequence. + /// or is null. + public static IObservable Delay(this IObservable source, Func> delayDurationSelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (delayDurationSelector == null) + { + throw new ArgumentNullException(nameof(delayDurationSelector)); + } + + return s_impl.Delay(source, delayDurationSelector); + } + + /// + /// Time shifts the observable sequence based on a subscription delay and a delay selector function for each element. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the delay sequences used to denote the delay duration of each element in the source sequence. + /// Source sequence to delay values for. + /// Sequence indicating the delay for the subscription to the source. + /// Selector function to retrieve a sequence indicating the delay for each given element. + /// Time-shifted sequence. + /// or or is null. + public static IObservable Delay(this IObservable source, IObservable subscriptionDelay, Func> delayDurationSelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (subscriptionDelay == null) + { + throw new ArgumentNullException(nameof(subscriptionDelay)); + } + + if (delayDurationSelector == null) + { + throw new ArgumentNullException(nameof(delayDurationSelector)); + } + + return s_impl.Delay(source, subscriptionDelay, delayDurationSelector); + } + + #endregion + + #endregion + + #region + DelaySubscription + + + /// + /// Time shifts the observable sequence by delaying the subscription with the specified relative time duration. + /// + /// The type of the elements in the source sequence. + /// Source sequence to delay subscription for. + /// Relative time shift of the subscription. + /// Time-shifted sequence. + /// is null. + /// is less than TimeSpan.Zero. + /// + /// + /// This operator is more efficient than Delay but postpones all side-effects of subscription and affects error propagation timing. + /// + /// + /// The side-effects of subscribing to the source sequence will be run on the default scheduler. Observer callbacks will not be affected. + /// + /// + public static IObservable DelaySubscription(this IObservable source, TimeSpan dueTime) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (dueTime < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(dueTime)); + } + + return s_impl.DelaySubscription(source, dueTime); + } + + /// + /// Time shifts the observable sequence by delaying the subscription with the specified relative time duration, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence. + /// Source sequence to delay subscription for. + /// Relative time shift of the subscription. + /// Scheduler to run the subscription delay timer on. + /// Time-shifted sequence. + /// or is null. + /// is less than TimeSpan.Zero. + /// + /// + /// This operator is more efficient than Delay but postpones all side-effects of subscription and affects error propagation timing. + /// + /// + /// The side-effects of subscribing to the source sequence will be run on the specified scheduler. Observer callbacks will not be affected. + /// + /// + public static IObservable DelaySubscription(this IObservable source, TimeSpan dueTime, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (dueTime < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(dueTime)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.DelaySubscription(source, dueTime, scheduler); + } + + /// + /// Time shifts the observable sequence by delaying the subscription to the specified absolute time. + /// + /// The type of the elements in the source sequence. + /// Source sequence to delay subscription for. + /// Absolute time to perform the subscription at. + /// Time-shifted sequence. + /// is null. + /// + /// + /// This operator is more efficient than Delay but postpones all side-effects of subscription and affects error propagation timing. + /// + /// + /// The side-effects of subscribing to the source sequence will be run on the default scheduler. Observer callbacks will not be affected. + /// + /// + public static IObservable DelaySubscription(this IObservable source, DateTimeOffset dueTime) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.DelaySubscription(source, dueTime); + } + + /// + /// Time shifts the observable sequence by delaying the subscription to the specified absolute time, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence. + /// Source sequence to delay subscription for. + /// Absolute time to perform the subscription at. + /// Scheduler to run the subscription delay timer on. + /// Time-shifted sequence. + /// or is null. + /// + /// + /// This operator is more efficient than Delay but postpones all side-effects of subscription and affects error propagation timing. + /// + /// + /// The side-effects of subscribing to the source sequence will be run on the specified scheduler. Observer callbacks will not be affected. + /// + /// + public static IObservable DelaySubscription(this IObservable source, DateTimeOffset dueTime, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.DelaySubscription(source, dueTime, scheduler); + } + + #endregion + + #region + Generate + + + /// + /// Generates an observable sequence by running a state-driven and temporal loop producing the sequence's elements. + /// + /// The type of the state used in the generator loop. + /// The type of the elements in the produced sequence. + /// Initial state. + /// Condition to terminate generation (upon returning false). + /// Iteration step function. + /// Selector function for results produced in the sequence. + /// Time selector function to control the speed of values being produced each iteration. + /// The generated sequence. + /// or or or is null. + public static IObservable Generate(TState initialState, Func condition, Func iterate, Func resultSelector, Func timeSelector) + { + if (condition == null) + { + throw new ArgumentNullException(nameof(condition)); + } + + if (iterate == null) + { + throw new ArgumentNullException(nameof(iterate)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + if (timeSelector == null) + { + throw new ArgumentNullException(nameof(timeSelector)); + } + + return s_impl.Generate(initialState, condition, iterate, resultSelector, timeSelector); + } + + /// + /// Generates an observable sequence by running a state-driven and temporal loop producing the sequence's elements, using the specified scheduler to run timers and to send out observer messages. + /// + /// The type of the state used in the generator loop. + /// The type of the elements in the produced sequence. + /// Initial state. + /// Condition to terminate generation (upon returning false). + /// Iteration step function. + /// Selector function for results produced in the sequence. + /// Time selector function to control the speed of values being produced each iteration. + /// Scheduler on which to run the generator loop. + /// The generated sequence. + /// or or or or is null. + public static IObservable Generate(TState initialState, Func condition, Func iterate, Func resultSelector, Func timeSelector, IScheduler scheduler) + { + if (condition == null) + { + throw new ArgumentNullException(nameof(condition)); + } + + if (iterate == null) + { + throw new ArgumentNullException(nameof(iterate)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + if (timeSelector == null) + { + throw new ArgumentNullException(nameof(timeSelector)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Generate(initialState, condition, iterate, resultSelector, timeSelector, scheduler); + } + + /// + /// Generates an observable sequence by running a state-driven and temporal loop producing the sequence's elements. + /// + /// The type of the state used in the generator loop. + /// The type of the elements in the produced sequence. + /// Initial state. + /// Condition to terminate generation (upon returning false). + /// Iteration step function. + /// Selector function for results produced in the sequence. + /// Time selector function to control the speed of values being produced each iteration. + /// The generated sequence. + /// or or or is null. + public static IObservable Generate(TState initialState, Func condition, Func iterate, Func resultSelector, Func timeSelector) + { + if (condition == null) + { + throw new ArgumentNullException(nameof(condition)); + } + + if (iterate == null) + { + throw new ArgumentNullException(nameof(iterate)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + if (timeSelector == null) + { + throw new ArgumentNullException(nameof(timeSelector)); + } + + return s_impl.Generate(initialState, condition, iterate, resultSelector, timeSelector); + } + + /// + /// Generates an observable sequence by running a state-driven and temporal loop producing the sequence's elements, using the specified scheduler to run timers and to send out observer messages. + /// + /// The type of the state used in the generator loop. + /// The type of the elements in the produced sequence. + /// Initial state. + /// Condition to terminate generation (upon returning false). + /// Iteration step function. + /// Selector function for results produced in the sequence. + /// Time selector function to control the speed of values being produced each iteration. + /// Scheduler on which to run the generator loop. + /// The generated sequence. + /// or or or or is null. + public static IObservable Generate(TState initialState, Func condition, Func iterate, Func resultSelector, Func timeSelector, IScheduler scheduler) + { + if (condition == null) + { + throw new ArgumentNullException(nameof(condition)); + } + + if (iterate == null) + { + throw new ArgumentNullException(nameof(iterate)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + if (timeSelector == null) + { + throw new ArgumentNullException(nameof(timeSelector)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Generate(initialState, condition, iterate, resultSelector, timeSelector, scheduler); + } + + #endregion + + #region + Interval + + + /// + /// Returns an observable sequence that produces a value after each period. + /// + /// Period for producing the values in the resulting sequence. If this value is equal to TimeSpan.Zero, the timer will recur as fast as possible. + /// An observable sequence that produces a value after each period. + /// is less than TimeSpan.Zero. + /// + /// Intervals are measured between the start of subsequent notifications, not between the end of the previous and the start of the next notification. + /// If the observer takes longer than the interval period to handle the message, the subsequent notification will be delivered immediately after the + /// current one has been handled. In case you need to control the time between the end and the start of consecutive notifications, consider using the + /// + /// operator instead. + /// + public static IObservable Interval(TimeSpan period) + { + if (period < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(period)); + } + + return s_impl.Interval(period); + } + + /// + /// Returns an observable sequence that produces a value after each period, using the specified scheduler to run timers and to send out observer messages. + /// + /// Period for producing the values in the resulting sequence. If this value is equal to TimeSpan.Zero, the timer will recur as fast as possible. + /// Scheduler to run the timer on. + /// An observable sequence that produces a value after each period. + /// is less than TimeSpan.Zero. + /// is null. + /// + /// Intervals are measured between the start of subsequent notifications, not between the end of the previous and the start of the next notification. + /// If the observer takes longer than the interval period to handle the message, the subsequent notification will be delivered immediately after the + /// current one has been handled. In case you need to control the time between the end and the start of consecutive notifications, consider using the + /// + /// operator instead. + /// + public static IObservable Interval(TimeSpan period, IScheduler scheduler) + { + if (period < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(period)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Interval(period, scheduler); + } + + #endregion + + #region + Sample + + + /// + /// Samples the observable sequence at each interval. + /// Upon each sampling tick, the latest element (if any) in the source sequence during the last sampling interval is sent to the resulting sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to sample. + /// Interval at which to sample. If this value is equal to TimeSpan.Zero, the scheduler will continuously sample the stream. + /// Sampled observable sequence. + /// is null. + /// is less than TimeSpan.Zero. + /// + /// Specifying a TimeSpan.Zero value for doesn't guarantee all source sequence elements will be preserved. This is a side-effect + /// of the asynchrony introduced by the scheduler, where the sampling action may not execute immediately, despite the TimeSpan.Zero due time. + /// + public static IObservable Sample(this IObservable source, TimeSpan interval) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (interval < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(interval)); + } + + return s_impl.Sample(source, interval); + } + + /// + /// Samples the observable sequence at each interval, using the specified scheduler to run sampling timers. + /// Upon each sampling tick, the latest element (if any) in the source sequence during the last sampling interval is sent to the resulting sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to sample. + /// Interval at which to sample. If this value is equal to TimeSpan.Zero, the scheduler will continuously sample the stream. + /// Scheduler to run the sampling timer on. + /// Sampled observable sequence. + /// or is null. + /// is less than TimeSpan.Zero. + /// + /// Specifying a TimeSpan.Zero value for doesn't guarantee all source sequence elements will be preserved. This is a side-effect + /// of the asynchrony introduced by the scheduler, where the sampling action may not execute immediately, despite the TimeSpan.Zero due time. + /// + public static IObservable Sample(this IObservable source, TimeSpan interval, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (interval < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(interval)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Sample(source, interval, scheduler); + } + + /// + /// Samples the source observable sequence using a sampler observable sequence producing sampling ticks. + /// Upon each sampling tick, the latest element (if any) in the source sequence during the last sampling interval is sent to the resulting sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the sampling sequence. + /// Source sequence to sample. + /// Sampling tick sequence. + /// Sampled observable sequence. + /// or is null. + public static IObservable Sample(this IObservable source, IObservable sampler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (sampler == null) + { + throw new ArgumentNullException(nameof(sampler)); + } + + return s_impl.Sample(source, sampler); + } + + #endregion + + #region + Skip + + + /// + /// Skips elements for the specified duration from the start of the observable source sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to skip elements for. + /// Duration for skipping elements from the start of the sequence. + /// An observable sequence with the elements skipped during the specified duration from the start of the source sequence. + /// is null. + /// is less than TimeSpan.Zero. + /// + /// + /// Specifying a TimeSpan.Zero value for doesn't guarantee no elements will be dropped from the start of the source sequence. + /// This is a side-effect of the asynchrony introduced by the scheduler, where the action that causes callbacks from the source sequence to be forwarded + /// may not execute immediately, despite the TimeSpan.Zero due time. + /// + /// + /// Errors produced by the source sequence are always forwarded to the result sequence, even if the error occurs before the . + /// + /// + public static IObservable Skip(this IObservable source, TimeSpan duration) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (duration < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(duration)); + } + + return s_impl.Skip(source, duration); + } + + /// + /// Skips elements for the specified duration from the start of the observable source sequence, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence. + /// Source sequence to skip elements for. + /// Duration for skipping elements from the start of the sequence. + /// Scheduler to run the timer on. + /// An observable sequence with the elements skipped during the specified duration from the start of the source sequence. + /// or is null. + /// is less than TimeSpan.Zero. + /// + /// + /// Specifying a TimeSpan.Zero value for doesn't guarantee no elements will be dropped from the start of the source sequence. + /// This is a side-effect of the asynchrony introduced by the scheduler, where the action that causes callbacks from the source sequence to be forwarded + /// may not execute immediately, despite the TimeSpan.Zero due time. + /// + /// + /// Errors produced by the source sequence are always forwarded to the result sequence, even if the error occurs before the . + /// + /// + public static IObservable Skip(this IObservable source, TimeSpan duration, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (duration < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(duration)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Skip(source, duration, scheduler); + } + + #endregion + + #region + SkipLast + + + /// + /// Skips elements for the specified duration from the end of the observable source sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to skip elements for. + /// Duration for skipping elements from the end of the sequence. + /// An observable sequence with the elements skipped during the specified duration from the end of the source sequence. + /// is null. + /// is less than TimeSpan.Zero. + /// + /// This operator accumulates a queue with a length enough to store elements received during the initial window. + /// As more elements are received, elements older than the specified are taken from the queue and produced on the + /// result sequence. This causes elements to be delayed with . + /// + public static IObservable SkipLast(this IObservable source, TimeSpan duration) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (duration < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(duration)); + } + + return s_impl.SkipLast(source, duration); + } + + /// + /// Skips elements for the specified duration from the end of the observable source sequence, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence. + /// Source sequence to skip elements for. + /// Duration for skipping elements from the end of the sequence. + /// Scheduler to run the timer on. + /// An observable sequence with the elements skipped during the specified duration from the end of the source sequence. + /// or is null. + /// is less than TimeSpan.Zero. + /// + /// This operator accumulates a queue with a length enough to store elements received during the initial window. + /// As more elements are received, elements older than the specified are taken from the queue and produced on the + /// result sequence. This causes elements to be delayed with . + /// + public static IObservable SkipLast(this IObservable source, TimeSpan duration, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (duration < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(duration)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.SkipLast(source, duration, scheduler); + } + + #endregion + + #region + SkipUntil + + + /// + /// Skips elements from the observable source sequence until the specified start time. + /// + /// The type of the elements in the source sequence. + /// Source sequence to skip elements for. + /// Time to start taking elements from the source sequence. If this value is less than or equal to DateTimeOffset.UtcNow, no elements will be skipped. + /// An observable sequence with the elements skipped until the specified start time. + /// is null. + /// + /// Errors produced by the source sequence are always forwarded to the result sequence, even if the error occurs before the . + /// + public static IObservable SkipUntil(this IObservable source, DateTimeOffset startTime) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.SkipUntil(source, startTime); + } + + /// + /// Skips elements from the observable source sequence until the specified start time, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence. + /// Source sequence to skip elements for. + /// Time to start taking elements from the source sequence. If this value is less than or equal to DateTimeOffset.UtcNow, no elements will be skipped. + /// Scheduler to run the timer on. + /// An observable sequence with the elements skipped until the specified start time. + /// or is null. + /// + /// Errors produced by the source sequence are always forwarded to the result sequence, even if the error occurs before the . + /// + public static IObservable SkipUntil(this IObservable source, DateTimeOffset startTime, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.SkipUntil(source, startTime, scheduler); + } + + #endregion + + #region + Take + + + /// + /// Takes elements for the specified duration from the start of the observable source sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to take elements from. + /// Duration for taking elements from the start of the sequence. + /// An observable sequence with the elements taken during the specified duration from the start of the source sequence. + /// is null. + /// is less than TimeSpan.Zero. + /// + /// Specifying a TimeSpan.Zero value for doesn't guarantee an empty sequence will be returned. This is a side-effect + /// of the asynchrony introduced by the scheduler, where the action that stops forwarding callbacks from the source sequence may not execute + /// immediately, despite the TimeSpan.Zero due time. + /// + public static IObservable Take(this IObservable source, TimeSpan duration) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (duration < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(duration)); + } + + return s_impl.Take(source, duration); + } + + /// + /// Takes elements for the specified duration from the start of the observable source sequence, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence. + /// Source sequence to take elements from. + /// Duration for taking elements from the start of the sequence. + /// Scheduler to run the timer on. + /// An observable sequence with the elements taken during the specified duration from the start of the source sequence. + /// or is null. + /// is less than TimeSpan.Zero. + /// + /// Specifying a TimeSpan.Zero value for doesn't guarantee an empty sequence will be returned. This is a side-effect + /// of the asynchrony introduced by the scheduler, where the action that stops forwarding callbacks from the source sequence may not execute + /// immediately, despite the TimeSpan.Zero due time. + /// + public static IObservable Take(this IObservable source, TimeSpan duration, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (duration < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(duration)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Take(source, duration, scheduler); + } + + #endregion + + #region + TakeLast + + + /// + /// Returns elements within the specified duration from the end of the observable source sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to take elements from. + /// Duration for taking elements from the end of the sequence. + /// An observable sequence with the elements taken during the specified duration from the end of the source sequence. + /// is null. + /// is less than TimeSpan.Zero. + /// + /// This operator accumulates a buffer with a length enough to store elements for any window during the lifetime of + /// the source sequence. Upon completion of the source sequence, this buffer is drained on the result sequence. This causes the result elements + /// to be delayed with . + /// + public static IObservable TakeLast(this IObservable source, TimeSpan duration) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (duration < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(duration)); + } + + return s_impl.TakeLast(source, duration); + } + + /// + /// Returns elements within the specified duration from the end of the observable source sequence, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence. + /// Source sequence to take elements from. + /// Duration for taking elements from the end of the sequence. + /// Scheduler to run the timer on. + /// An observable sequence with the elements taken during the specified duration from the end of the source sequence. + /// or is null. + /// is less than TimeSpan.Zero. + /// + /// This operator accumulates a buffer with a length enough to store elements for any window during the lifetime of + /// the source sequence. Upon completion of the source sequence, this buffer is drained on the result sequence. This causes the result elements + /// to be delayed with . + /// + public static IObservable TakeLast(this IObservable source, TimeSpan duration, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (duration < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(duration)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.TakeLast(source, duration, scheduler); + } + + /// + /// Returns elements within the specified duration from the end of the observable source sequence, using the specified schedulers to run timers and to drain the collected elements. + /// + /// The type of the elements in the source sequence. + /// Source sequence to take elements from. + /// Duration for taking elements from the end of the sequence. + /// Scheduler to run the timer on. + /// Scheduler to drain the collected elements. + /// An observable sequence with the elements taken during the specified duration from the end of the source sequence. + /// or or is null. + /// is less than TimeSpan.Zero. + /// + /// This operator accumulates a buffer with a length enough to store elements for any window during the lifetime of + /// the source sequence. Upon completion of the source sequence, this buffer is drained on the result sequence. This causes the result elements + /// to be delayed with . + /// + public static IObservable TakeLast(this IObservable source, TimeSpan duration, IScheduler timerScheduler, IScheduler loopScheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (duration < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(duration)); + } + + if (timerScheduler == null) + { + throw new ArgumentNullException(nameof(timerScheduler)); + } + + if (loopScheduler == null) + { + throw new ArgumentNullException(nameof(loopScheduler)); + } + + return s_impl.TakeLast(source, duration, timerScheduler, loopScheduler); + } + + #endregion + + #region + TakeLastBuffer + + + /// + /// Returns a list with the elements within the specified duration from the end of the observable source sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to take elements from. + /// Duration for taking elements from the end of the sequence. + /// An observable sequence containing a single list with the elements taken during the specified duration from the end of the source sequence. + /// is null. + /// is less than TimeSpan.Zero. + /// + /// This operator accumulates a buffer with a length enough to store elements for any window during the lifetime of + /// the source sequence. Upon completion of the source sequence, this buffer is produced on the result sequence. + /// + public static IObservable> TakeLastBuffer(this IObservable source, TimeSpan duration) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (duration < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(duration)); + } + + return s_impl.TakeLastBuffer(source, duration); + } + + /// + /// Returns a list with the elements within the specified duration from the end of the observable source sequence, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence. + /// Source sequence to take elements from. + /// Duration for taking elements from the end of the sequence. + /// Scheduler to run the timer on. + /// An observable sequence containing a single list with the elements taken during the specified duration from the end of the source sequence. + /// or is null. + /// is less than TimeSpan.Zero. + /// + /// This operator accumulates a buffer with a length enough to store elements for any window during the lifetime of + /// the source sequence. Upon completion of the source sequence, this buffer is produced on the result sequence. + /// + public static IObservable> TakeLastBuffer(this IObservable source, TimeSpan duration, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (duration < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(duration)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.TakeLastBuffer(source, duration, scheduler); + } + + #endregion + + #region + TakeUntil + + + /// + /// Takes elements for the specified duration until the specified end time. + /// + /// The type of the elements in the source sequence. + /// Source sequence to take elements from. + /// Time to stop taking elements from the source sequence. If this value is less than or equal to DateTimeOffset.UtcNow, the result stream will complete immediately. + /// An observable sequence with the elements taken until the specified end time. + /// is null. + public static IObservable TakeUntil(this IObservable source, DateTimeOffset endTime) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.TakeUntil(source, endTime); + } + + /// + /// Takes elements for the specified duration until the specified end time, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence. + /// Source sequence to take elements from. + /// Time to stop taking elements from the source sequence. If this value is less than or equal to DateTimeOffset.UtcNow, the result stream will complete immediately. + /// Scheduler to run the timer on. + /// An observable sequence with the elements taken until the specified end time. + /// or is null. + public static IObservable TakeUntil(this IObservable source, DateTimeOffset endTime, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.TakeUntil(source, endTime, scheduler); + } + + #endregion + + #region + Throttle + + + /// + /// Ignores elements from an observable sequence which are followed by another element within a specified relative time duration. + /// + /// The type of the elements in the source sequence. + /// Source sequence to throttle. + /// Throttling duration for each element. + /// The throttled sequence. + /// is null. + /// is less than TimeSpan.Zero. + /// + /// + /// This operator throttles the source sequence by holding on to each element for the duration specified in . If another + /// element is produced within this time window, the element is dropped and a new timer is started for the current element, repeating this whole + /// process. For streams that never have gaps larger than or equal to between elements, the resulting stream won't + /// produce any elements. In order to reduce the volume of a stream whilst guaranteeing the periodic production of elements, consider using the + /// Observable.Sample set of operators. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing throttling timers to be scheduled + /// that are due immediately. However, this doesn't guarantee all elements will be retained in the result sequence. This is a side-effect of the + /// asynchrony introduced by the scheduler, where the action to forward the current element may not execute immediately, despite the TimeSpan.Zero + /// due time. In such cases, the next element may arrive before the scheduler gets a chance to run the throttling action. + /// + /// + public static IObservable Throttle(this IObservable source, TimeSpan dueTime) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (dueTime < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(dueTime)); + } + + return s_impl.Throttle(source, dueTime); + } + + /// + /// Ignores elements from an observable sequence which are followed by another element within a specified relative time duration, using the specified scheduler to run throttling timers. + /// + /// The type of the elements in the source sequence. + /// Source sequence to throttle. + /// Throttling duration for each element. + /// Scheduler to run the throttle timers on. + /// The throttled sequence. + /// or is null. + /// is less than TimeSpan.Zero. + /// + /// + /// This operator throttles the source sequence by holding on to each element for the duration specified in . If another + /// element is produced within this time window, the element is dropped and a new timer is started for the current element, repeating this whole + /// process. For streams that never have gaps larger than or equal to between elements, the resulting stream won't + /// produce any elements. In order to reduce the volume of a stream whilst guaranteeing the periodic production of elements, consider using the + /// Observable.Sample set of operators. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing throttling timers to be scheduled + /// that are due immediately. However, this doesn't guarantee all elements will be retained in the result sequence. This is a side-effect of the + /// asynchrony introduced by the scheduler, where the action to forward the current element may not execute immediately, despite the TimeSpan.Zero + /// due time. In such cases, the next element may arrive before the scheduler gets a chance to run the throttling action. + /// + /// + public static IObservable Throttle(this IObservable source, TimeSpan dueTime, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (dueTime < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(dueTime)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Throttle(source, dueTime, scheduler); + } + + /// + /// Ignores elements from an observable sequence which are followed by another value within a computed throttle duration. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the throttle sequences selected for each element in the source sequence. + /// Source sequence to throttle. + /// Selector function to retrieve a sequence indicating the throttle duration for each given element. + /// The throttled sequence. + /// or is null. + /// + /// This operator throttles the source sequence by holding on to each element for the duration denoted by . + /// If another element is produced within this time window, the element is dropped and a new timer is started for the current element, repeating this + /// whole process. For streams where the duration computed by applying the to each element overlaps with + /// the occurrence of the successor element, the resulting stream won't produce any elements. In order to reduce the volume of a stream whilst + /// guaranteeing the periodic production of elements, consider using the Observable.Sample set of operators. + /// + public static IObservable Throttle(this IObservable source, Func> throttleDurationSelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (throttleDurationSelector == null) + { + throw new ArgumentNullException(nameof(throttleDurationSelector)); + } + + return s_impl.Throttle(source, throttleDurationSelector); + } + + #endregion + + #region + TimeInterval + + + /// + /// Records the time interval between consecutive elements in an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to record time intervals for. + /// An observable sequence with time interval information on elements. + /// is null. + public static IObservable> TimeInterval(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.TimeInterval(source); + } + + /// + /// Records the time interval between consecutive elements in an observable sequence, using the specified scheduler to compute time intervals. + /// + /// The type of the elements in the source sequence. + /// Source sequence to record time intervals for. + /// Scheduler used to compute time intervals. + /// An observable sequence with time interval information on elements. + /// or is null. + public static IObservable> TimeInterval(this IObservable source, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.TimeInterval(source, scheduler); + } + + #endregion + + #region + Timeout + + + #region TimeSpan + + /// + /// Applies a timeout policy for each element in the observable sequence. + /// If the next element isn't received within the specified timeout duration starting from its predecessor, a TimeoutException is propagated to the observer. + /// + /// The type of the elements in the source sequence. + /// Source sequence to perform a timeout for. + /// Maximum duration between values before a timeout occurs. + /// The source sequence with a TimeoutException in case of a timeout. + /// is null. + /// is less than TimeSpan.Zero. + /// (Asynchronous) If no element is produced within from the previous element. + /// + /// + /// In case you only want to timeout on the first element, consider using the + /// operator applied to the source sequence and a delayed sequence. Alternatively, the general-purpose overload + /// of Timeout, can be used. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing timeout timers to be scheduled that are due + /// immediately. However, this doesn't guarantee a timeout will occur, even for the first element. This is a side-effect of the asynchrony introduced by the + /// scheduler, where the action to propagate a timeout may not execute immediately, despite the TimeSpan.Zero due time. In such cases, the next element may + /// arrive before the scheduler gets a chance to run the timeout action. + /// + /// + public static IObservable Timeout(this IObservable source, TimeSpan dueTime) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (dueTime < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(dueTime)); + } + + return s_impl.Timeout(source, dueTime); + } + + /// + /// Applies a timeout policy for each element in the observable sequence, using the specified scheduler to run timeout timers. + /// If the next element isn't received within the specified timeout duration starting from its predecessor, a TimeoutException is propagated to the observer. + /// + /// The type of the elements in the source sequence. + /// Source sequence to perform a timeout for. + /// Maximum duration between values before a timeout occurs. + /// Scheduler to run the timeout timers on. + /// The source sequence with a TimeoutException in case of a timeout. + /// or is null. + /// is less than TimeSpan.Zero. + /// (Asynchronous) If no element is produced within from the previous element. + /// + /// + /// In case you only want to timeout on the first element, consider using the + /// operator applied to the source sequence and a delayed sequence. Alternatively, the general-purpose overload + /// of Timeout, can be used. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing timeout timers to be scheduled that are due + /// immediately. However, this doesn't guarantee a timeout will occur, even for the first element. This is a side-effect of the asynchrony introduced by the + /// scheduler, where the action to propagate a timeout may not execute immediately, despite the TimeSpan.Zero due time. In such cases, the next element may + /// arrive before the scheduler gets a chance to run the timeout action. + /// + /// + public static IObservable Timeout(this IObservable source, TimeSpan dueTime, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (dueTime < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(dueTime)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Timeout(source, dueTime, scheduler); + } + + /// + /// Applies a timeout policy for each element in the observable sequence. + /// If the next element isn't received within the specified timeout duration starting from its predecessor, the other observable sequence is used to produce future messages from that point on. + /// + /// The type of the elements in the source sequence and the other sequence used upon a timeout. + /// Source sequence to perform a timeout for. + /// Maximum duration between values before a timeout occurs. + /// Sequence to return in case of a timeout. + /// The source sequence switching to the other sequence in case of a timeout. + /// or is null. + /// is less than TimeSpan.Zero. + /// + /// + /// In case you only want to timeout on the first element, consider using the + /// operator applied to the source sequence and a delayed sequence. Alternatively, the general-purpose overload + /// of Timeout, can be used. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing timeout timers to be scheduled that are due + /// immediately. However, this doesn't guarantee a timeout will occur, even for the first element. This is a side-effect of the asynchrony introduced by the + /// scheduler, where the action to propagate a timeout may not execute immediately, despite the TimeSpan.Zero due time. In such cases, the next element may + /// arrive before the scheduler gets a chance to run the timeout action. + /// + /// + public static IObservable Timeout(this IObservable source, TimeSpan dueTime, IObservable other) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (dueTime < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(dueTime)); + } + + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return s_impl.Timeout(source, dueTime, other); + } + + /// + /// Applies a timeout policy for each element in the observable sequence, using the specified scheduler to run timeout timers. + /// If the next element isn't received within the specified timeout duration starting from its predecessor, the other observable sequence is used to produce future messages from that point on. + /// + /// The type of the elements in the source sequence and the other sequence used upon a timeout. + /// Source sequence to perform a timeout for. + /// Maximum duration between values before a timeout occurs. + /// Sequence to return in case of a timeout. + /// Scheduler to run the timeout timers on. + /// The source sequence switching to the other sequence in case of a timeout. + /// or or is null. + /// is less than TimeSpan.Zero. + /// + /// + /// In case you only want to timeout on the first element, consider using the + /// operator applied to the source sequence and a delayed sequence. Alternatively, the general-purpose overload + /// of Timeout, can be used. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing timeout timers to be scheduled that are due + /// immediately. However, this doesn't guarantee a timeout will occur, even for the first element. This is a side-effect of the asynchrony introduced by the + /// scheduler, where the action to propagate a timeout may not execute immediately, despite the TimeSpan.Zero due time. In such cases, the next element may + /// arrive before the scheduler gets a chance to run the timeout action. + /// + /// + public static IObservable Timeout(this IObservable source, TimeSpan dueTime, IObservable other, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (dueTime < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(dueTime)); + } + + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Timeout(source, dueTime, other, scheduler); + } + + #endregion + + #region DateTimeOffset + + /// + /// Applies a timeout policy to the observable sequence based on an absolute time. + /// If the sequence doesn't terminate before the specified absolute due time, a TimeoutException is propagated to the observer. + /// + /// The type of the elements in the source sequence. + /// Source sequence to perform a timeout for. + /// Time when a timeout occurs. If this value is less than or equal to DateTimeOffset.UtcNow, the timeout occurs immediately. + /// The source sequence with a TimeoutException in case of a timeout. + /// is null. + /// (Asynchronous) If the sequence hasn't terminated before . + /// + /// In case you only want to timeout on the first element, consider using the + /// operator applied to the source sequence and a delayed sequence. Alternatively, the general-purpose overload + /// of Timeout, can be used. + /// + public static IObservable Timeout(this IObservable source, DateTimeOffset dueTime) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Timeout(source, dueTime); + } + + /// + /// Applies a timeout policy to the observable sequence based on an absolute time, using the specified scheduler to run timeout timers. + /// If the sequence doesn't terminate before the specified absolute due time, a TimeoutException is propagated to the observer. + /// + /// The type of the elements in the source sequence. + /// Source sequence to perform a timeout for. + /// Time when a timeout occurs. If this value is less than or equal to DateTimeOffset.UtcNow, the timeout occurs immediately. + /// Scheduler to run the timeout timers on. + /// The source sequence with a TimeoutException in case of a timeout. + /// or is null. + /// (Asynchronous) If the sequence hasn't terminated before . + /// + /// In case you only want to timeout on the first element, consider using the + /// operator applied to the source sequence and a delayed sequence. Alternatively, the general-purpose overload + /// of Timeout, can be used. + /// + public static IObservable Timeout(this IObservable source, DateTimeOffset dueTime, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Timeout(source, dueTime, scheduler); + } + + /// + /// Applies a timeout policy to the observable sequence based on an absolute time. + /// If the sequence doesn't terminate before the specified absolute due time, the other observable sequence is used to produce future messages from that point on. + /// + /// The type of the elements in the source sequence and the other sequence used upon a timeout. + /// Source sequence to perform a timeout for. + /// Time when a timeout occurs. If this value is less than or equal to DateTimeOffset.UtcNow, the timeout occurs immediately. + /// Sequence to return in case of a timeout. + /// The source sequence switching to the other sequence in case of a timeout. + /// or is null. + /// + /// In case you only want to timeout on the first element, consider using the + /// operator applied to the source sequence and a delayed sequence. Alternatively, the general-purpose overload + /// of Timeout, can be used. + /// + public static IObservable Timeout(this IObservable source, DateTimeOffset dueTime, IObservable other) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return s_impl.Timeout(source, dueTime, other); + } + + /// + /// Applies a timeout policy to the observable sequence based on an absolute time, using the specified scheduler to run timeout timers. + /// If the sequence doesn't terminate before the specified absolute due time, the other observable sequence is used to produce future messages from that point on. + /// + /// The type of the elements in the source sequence and the other sequence used upon a timeout. + /// Source sequence to perform a timeout for. + /// Time when a timeout occurs. If this value is less than or equal to DateTimeOffset.UtcNow, the timeout occurs immediately. + /// Sequence to return in case of a timeout. + /// Scheduler to run the timeout timers on. + /// The source sequence switching to the other sequence in case of a timeout. + /// or or is null. + /// + /// In case you only want to timeout on the first element, consider using the + /// operator applied to the source sequence and a delayed sequence. Alternatively, the general-purpose overload + /// of Timeout, can be used. + /// + public static IObservable Timeout(this IObservable source, DateTimeOffset dueTime, IObservable other, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return s_impl.Timeout(source, dueTime, other, scheduler); + } + + #endregion + + #region Duration selector + + /// + /// Applies a timeout policy to the observable sequence based on a timeout duration computed for each element. + /// If the next element isn't received within the computed duration starting from its predecessor, a TimeoutException is propagated to the observer. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the timeout sequences used to indicate the timeout duration for each element in the source sequence. + /// Source sequence to perform a timeout for. + /// Selector to retrieve an observable sequence that represents the timeout between the current element and the next element. + /// The source sequence with a TimeoutException in case of a timeout. + /// or is null. + public static IObservable Timeout(this IObservable source, Func> timeoutDurationSelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (timeoutDurationSelector == null) + { + throw new ArgumentNullException(nameof(timeoutDurationSelector)); + } + + return s_impl.Timeout(source, timeoutDurationSelector); + } + + /// + /// Applies a timeout policy to the observable sequence based on a timeout duration computed for each element. + /// If the next element isn't received within the computed duration starting from its predecessor, the other observable sequence is used to produce future messages from that point on. + /// + /// The type of the elements in the source sequence and the other sequence used upon a timeout. + /// The type of the elements in the timeout sequences used to indicate the timeout duration for each element in the source sequence. + /// Source sequence to perform a timeout for. + /// Selector to retrieve an observable sequence that represents the timeout between the current element and the next element. + /// Sequence to return in case of a timeout. + /// The source sequence switching to the other sequence in case of a timeout. + /// or or is null. + public static IObservable Timeout(this IObservable source, Func> timeoutDurationSelector, IObservable other) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (timeoutDurationSelector == null) + { + throw new ArgumentNullException(nameof(timeoutDurationSelector)); + } + + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return s_impl.Timeout(source, timeoutDurationSelector, other); + } + + /// + /// Applies a timeout policy to the observable sequence based on an initial timeout duration for the first element, and a timeout duration computed for each subsequent element. + /// If the next element isn't received within the computed duration starting from its predecessor, a TimeoutException is propagated to the observer. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the timeout sequences used to indicate the timeout duration for each element in the source sequence. + /// Source sequence to perform a timeout for. + /// Observable sequence that represents the timeout for the first element. + /// Selector to retrieve an observable sequence that represents the timeout between the current element and the next element. + /// The source sequence with a TimeoutException in case of a timeout. + /// or or is null. + public static IObservable Timeout(this IObservable source, IObservable firstTimeout, Func> timeoutDurationSelector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (firstTimeout == null) + { + throw new ArgumentNullException(nameof(firstTimeout)); + } + + if (timeoutDurationSelector == null) + { + throw new ArgumentNullException(nameof(timeoutDurationSelector)); + } + + return s_impl.Timeout(source, firstTimeout, timeoutDurationSelector); + } + + /// + /// Applies a timeout policy to the observable sequence based on an initial timeout duration for the first element, and a timeout duration computed for each subsequent element. + /// If the next element isn't received within the computed duration starting from its predecessor, the other observable sequence is used to produce future messages from that point on. + /// + /// The type of the elements in the source sequence and the other sequence used upon a timeout. + /// The type of the elements in the timeout sequences used to indicate the timeout duration for each element in the source sequence. + /// Source sequence to perform a timeout for. + /// Observable sequence that represents the timeout for the first element. + /// Selector to retrieve an observable sequence that represents the timeout between the current element and the next element. + /// Sequence to return in case of a timeout. + /// The source sequence switching to the other sequence in case of a timeout. + /// or or or is null. + public static IObservable Timeout(this IObservable source, IObservable firstTimeout, Func> timeoutDurationSelector, IObservable other) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (firstTimeout == null) + { + throw new ArgumentNullException(nameof(firstTimeout)); + } + + if (timeoutDurationSelector == null) + { + throw new ArgumentNullException(nameof(timeoutDurationSelector)); + } + + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return s_impl.Timeout(source, firstTimeout, timeoutDurationSelector, other); + } + + #endregion + + #endregion + + #region + Timer + + + /// + /// Returns an observable sequence that produces a single value after the specified relative due time has elapsed. + /// + /// Relative time at which to produce the value. If this value is less than or equal to TimeSpan.Zero, the timer will fire as soon as possible. + /// An observable sequence that produces a value after the due time has elapsed. + public static IObservable Timer(TimeSpan dueTime) + { + return s_impl.Timer(dueTime); + } + + /// + /// Returns an observable sequence that produces a single value at the specified absolute due time. + /// + /// Absolute time at which to produce the value. If this value is less than or equal to DateTimeOffset.UtcNow, the timer will fire as soon as possible. + /// An observable sequence that produces a value at due time. + public static IObservable Timer(DateTimeOffset dueTime) + { + return s_impl.Timer(dueTime); + } + + /// + /// Returns an observable sequence that periodically produces a value after the specified initial relative due time has elapsed. + /// + /// Relative time at which to produce the first value. If this value is less than or equal to TimeSpan.Zero, the timer will fire as soon as possible. + /// Period to produce subsequent values. If this value is equal to TimeSpan.Zero, the timer will recur as fast as possible. + /// An observable sequence that produces a value after due time has elapsed and then after each period. + /// is less than TimeSpan.Zero. + public static IObservable Timer(TimeSpan dueTime, TimeSpan period) + { + if (period < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(period)); + } + + return s_impl.Timer(dueTime, period); + } + + /// + /// Returns an observable sequence that periodically produces a value starting at the specified initial absolute due time. + /// + /// Absolute time at which to produce the first value. If this value is less than or equal to DateTimeOffset.UtcNow, the timer will fire as soon as possible. + /// Period to produce subsequent values. If this value is equal to TimeSpan.Zero, the timer will recur as fast as possible. + /// An observable sequence that produces a value at due time and then after each period. + /// is less than TimeSpan.Zero. + public static IObservable Timer(DateTimeOffset dueTime, TimeSpan period) + { + if (period < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(period)); + } + + return s_impl.Timer(dueTime, period); + } + + /// + /// Returns an observable sequence that produces a single value after the specified relative due time has elapsed, using the specified scheduler to run the timer. + /// + /// Relative time at which to produce the value. If this value is less than or equal to TimeSpan.Zero, the timer will fire as soon as possible. + /// Scheduler to run the timer on. + /// An observable sequence that produces a value after the due time has elapsed. + /// is null. + public static IObservable Timer(TimeSpan dueTime, IScheduler scheduler) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Timer(dueTime, scheduler); + } + + /// + /// Returns an observable sequence that produces a single value at the specified absolute due time, using the specified scheduler to run the timer. + /// + /// Absolute time at which to produce the value. If this value is less than or equal to DateTimeOffset.UtcNow, the timer will fire as soon as possible. + /// Scheduler to run the timer on. + /// An observable sequence that produces a value at due time. + /// is null. + public static IObservable Timer(DateTimeOffset dueTime, IScheduler scheduler) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Timer(dueTime, scheduler); + } + + /// + /// Returns an observable sequence that periodically produces a value after the specified initial relative due time has elapsed, using the specified scheduler to run timers. + /// + /// Relative time at which to produce the first value. If this value is less than or equal to TimeSpan.Zero, the timer will fire as soon as possible. + /// Period to produce subsequent values. If this value is equal to TimeSpan.Zero, the timer will recur as fast as possible. + /// Scheduler to run timers on. + /// An observable sequence that produces a value after due time has elapsed and then each period. + /// is less than TimeSpan.Zero. + /// is null. + public static IObservable Timer(TimeSpan dueTime, TimeSpan period, IScheduler scheduler) + { + if (period < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(period)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Timer(dueTime, period, scheduler); + } + + /// + /// Returns an observable sequence that periodically produces a value starting at the specified initial absolute due time, using the specified scheduler to run timers. + /// + /// Absolute time at which to produce the first value. If this value is less than or equal to DateTimeOffset.UtcNow, the timer will fire as soon as possible. + /// Period to produce subsequent values. If this value is equal to TimeSpan.Zero, the timer will recur as fast as possible. + /// Scheduler to run timers on. + /// An observable sequence that produces a value at due time and then after each period. + /// is less than TimeSpan.Zero. + /// is null. + public static IObservable Timer(DateTimeOffset dueTime, TimeSpan period, IScheduler scheduler) + { + if (period < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(period)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Timer(dueTime, period, scheduler); + } + + #endregion + + #region + Timestamp + + + /// + /// Timestamps each element in an observable sequence using the local system clock. + /// + /// The type of the elements in the source sequence. + /// Source sequence to timestamp elements for. + /// An observable sequence with timestamp information on elements. + /// is null. + public static IObservable> Timestamp(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.Timestamp(source); + } + + /// + /// Timestamp each element in an observable sequence using the clock of the specified scheduler. + /// + /// The type of the elements in the source sequence. + /// Source sequence to timestamp elements for. + /// Scheduler used to compute timestamps. + /// An observable sequence with timestamp information on elements. + /// or is null. + public static IObservable> Timestamp(this IObservable source, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Timestamp(source, scheduler); + } + + #endregion + + #region + Window + + + #region TimeSpan only + + /// + /// Projects each element of an observable sequence into consecutive non-overlapping windows which are produced based on timing information. + /// + /// The type of the elements in the source sequence, and in the windows in the result sequence. + /// Source sequence to produce windows over. + /// Length of each window. + /// The sequence of windows. + /// is null. + /// is less than TimeSpan.Zero. + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create windows as fast as it can. + /// Because all source sequence elements end up in one of the windows, some windows won't have a zero time span. This is a side-effect of the asynchrony introduced + /// by the scheduler, where the action to close the current window and to create a new window may not execute immediately, despite the TimeSpan.Zero due time. + /// + public static IObservable> Window(this IObservable source, TimeSpan timeSpan) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (timeSpan < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(timeSpan)); + } + + return s_impl.Window(source, timeSpan); + } + + /// + /// Projects each element of an observable sequence into consecutive non-overlapping windows which are produced based on timing information, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence, and in the windows in the result sequence. + /// Source sequence to produce windows over. + /// Length of each window. + /// Scheduler to run windowing timers on. + /// An observable sequence of windows. + /// or is null. + /// is less than TimeSpan.Zero. + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create windows as fast as it can. + /// Because all source sequence elements end up in one of the windows, some windows won't have a zero time span. This is a side-effect of the asynchrony introduced + /// by the scheduler, where the action to close the current window and to create a new window may not execute immediately, despite the TimeSpan.Zero due time. + /// + public static IObservable> Window(this IObservable source, TimeSpan timeSpan, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (timeSpan < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(timeSpan)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Window(source, timeSpan, scheduler); + } + + /// + /// Projects each element of an observable sequence into zero or more windows which are produced based on timing information. + /// + /// The type of the elements in the source sequence, and in the windows in the result sequence. + /// Source sequence to produce windows over. + /// Length of each window. + /// Interval between creation of consecutive windows. + /// An observable sequence of windows. + /// is null. + /// or is less than TimeSpan.Zero. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create windows with minimum duration + /// length. However, some windows won't have a zero time span. This is a side-effect of the asynchrony introduced by the scheduler, where the action to close the + /// current window may not execute immediately, despite the TimeSpan.Zero due time. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create windows as fast as it can. + /// However, this doesn't mean all windows will start at the beginning of the source sequence. This is a side-effect of the asynchrony introduced by the scheduler, + /// where the action to create a new window may not execute immediately, despite the TimeSpan.Zero due time. + /// + /// + public static IObservable> Window(this IObservable source, TimeSpan timeSpan, TimeSpan timeShift) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (timeSpan < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(timeSpan)); + } + + if (timeShift < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(timeShift)); + } + + return s_impl.Window(source, timeSpan, timeShift); + } + + /// + /// Projects each element of an observable sequence into zero or more windows which are produced based on timing information, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence, and in the windows in the result sequence. + /// Source sequence to produce windows over. + /// Length of each window. + /// Interval between creation of consecutive windows. + /// Scheduler to run windowing timers on. + /// An observable sequence of windows. + /// or is null. + /// or is less than TimeSpan.Zero. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create windows with minimum duration + /// length. However, some windows won't have a zero time span. This is a side-effect of the asynchrony introduced by the scheduler, where the action to close the + /// current window may not execute immediately, despite the TimeSpan.Zero due time. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create windows as fast as it can. + /// However, this doesn't mean all windows will start at the beginning of the source sequence. This is a side-effect of the asynchrony introduced by the scheduler, + /// where the action to create a new window may not execute immediately, despite the TimeSpan.Zero due time. + /// + /// + public static IObservable> Window(this IObservable source, TimeSpan timeSpan, TimeSpan timeShift, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (timeSpan < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(timeSpan)); + } + + if (timeShift < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(timeShift)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Window(source, timeSpan, timeShift, scheduler); + } + + #endregion + + #region TimeSpan + int + + /// + /// Projects each element of an observable sequence into a window that is completed when either it's full or a given amount of time has elapsed. + /// A useful real-world analogy of this overload is the behavior of a ferry leaving the dock when all seats are taken, or at the scheduled time of departure, whichever event occurs first. + /// + /// The type of the elements in the source sequence, and in the windows in the result sequence. + /// Source sequence to produce windows over. + /// Maximum time length of a window. + /// Maximum element count of a window. + /// An observable sequence of windows. + /// is null. + /// is less than TimeSpan.Zero. -or- is less than or equal to zero. + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create windows as fast as it can. + /// Because all source sequence elements end up in one of the windows, some windows won't have a zero time span. This is a side-effect of the asynchrony introduced + /// by the scheduler, where the action to close the current window and to create a new window may not execute immediately, despite the TimeSpan.Zero due time. + /// + public static IObservable> Window(this IObservable source, TimeSpan timeSpan, int count) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (timeSpan < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(timeSpan)); + } + + if (count <= 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + return s_impl.Window(source, timeSpan, count); + } + + /// + /// Projects each element of an observable sequence into a window that is completed when either it's full or a given amount of time has elapsed, using the specified scheduler to run timers. + /// A useful real-world analogy of this overload is the behavior of a ferry leaving the dock when all seats are taken, or at the scheduled time of departure, whichever event occurs first. + /// + /// The type of the elements in the source sequence, and in the windows in the result sequence. + /// Source sequence to produce windows over. + /// Maximum time length of a window. + /// Maximum element count of a window. + /// Scheduler to run windowing timers on. + /// An observable sequence of windows. + /// or is null. + /// is less than TimeSpan.Zero. -or- is less than or equal to zero. + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create windows as fast as it can. + /// Because all source sequence elements end up in one of the windows, some windows won't have a zero time span. This is a side-effect of the asynchrony introduced + /// by the scheduler, where the action to close the current window and to create a new window may not execute immediately, despite the TimeSpan.Zero due time. + /// + public static IObservable> Window(this IObservable source, TimeSpan timeSpan, int count, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (timeSpan < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(timeSpan)); + } + + if (count <= 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Window(source, timeSpan, count, scheduler); + } + + #endregion + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/AddRef.cs b/LibExternal/System.Reactive/Linq/Observable/AddRef.cs new file mode 100644 index 0000000..63d0e1c --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/AddRef.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal class AddRef : Producer._> + { + private readonly IObservable _source; + private readonly RefCountDisposable _refCount; + + public AddRef(IObservable source, RefCountDisposable refCount) + { + _source = source; + _refCount = refCount; + } + + protected override _ CreateSink(IObserver observer) => new(observer, _refCount.GetDisposable()); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly IDisposable _refCountDisposable; + + public _(IObserver observer, IDisposable refCountDisposable) + : base(observer) + { + _refCountDisposable = refCountDisposable; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _refCountDisposable.Dispose(); + } + + base.Dispose(disposing); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Aggregate.cs b/LibExternal/System.Reactive/Linq/Observable/Aggregate.cs new file mode 100644 index 0000000..140921d --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Aggregate.cs @@ -0,0 +1,218 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Aggregate : Producer._> + { + private readonly IObservable _source; + private readonly Func _accumulator; + + public Aggregate(IObservable source, Func accumulator) + { + _source = source; + _accumulator = accumulator; + } + + protected override _ CreateSink(IObserver observer) => new(_accumulator, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly Func _accumulator; + private TSource? _accumulation; + private bool _hasAccumulation; + + public _(Func accumulator, IObserver observer) + : base(observer) + { + _accumulator = accumulator; + } + + public override void OnNext(TSource value) + { + if (!_hasAccumulation) + { + _accumulation = value; + _hasAccumulation = true; + } + else + { + try + { + _accumulation = _accumulator(_accumulation!, value); + } + catch (Exception exception) + { + _accumulation = default; + ForwardOnError(exception); + } + } + } + + public override void OnError(Exception error) + { + _accumulation = default; + ForwardOnError(error); + } + + public override void OnCompleted() + { + if (!_hasAccumulation) + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + else + { + var accumulation = _accumulation!; + _accumulation = default; + ForwardOnNext(accumulation); + ForwardOnCompleted(); + } + } + } + } + + internal sealed class Aggregate : Producer._> + { + private readonly IObservable _source; + private readonly TAccumulate _seed; + private readonly Func _accumulator; + + public Aggregate(IObservable source, TAccumulate seed, Func accumulator) + { + _source = source; + _seed = seed; + _accumulator = accumulator; + } + + protected override _ CreateSink(IObserver observer) => new(_seed, _accumulator, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private readonly Func _accumulator; + private TAccumulate? _accumulation; + + public _(TAccumulate seed, Func accumulator, IObserver observer) + : base(observer) + { + _accumulator = accumulator; + _accumulation = seed; + } + + public override void OnNext(TSource value) + { + try + { + _accumulation = _accumulator(_accumulation!, value); + } + catch (Exception exception) + { + _accumulation = default; + ForwardOnError(exception); + } + } + + public override void OnError(Exception error) + { + _accumulation = default; + ForwardOnError(error); + } + + public override void OnCompleted() + { + var accumulation = _accumulation!; + _accumulation = default; + ForwardOnNext(accumulation); + ForwardOnCompleted(); + } + } + } + + internal sealed class Aggregate : Producer._> + { + private readonly IObservable _source; + private readonly TAccumulate _seed; + private readonly Func _accumulator; + private readonly Func _resultSelector; + + public Aggregate(IObservable source, TAccumulate seed, Func accumulator, Func resultSelector) + { + _source = source; + _seed = seed; + _accumulator = accumulator; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private readonly Func _accumulator; + private readonly Func _resultSelector; + + private TAccumulate? _accumulation; + + public _(Aggregate parent, IObserver observer) + : base(observer) + { + _accumulator = parent._accumulator; + _resultSelector = parent._resultSelector; + + _accumulation = parent._seed; + } + + public override void OnNext(TSource value) + { + try + { + _accumulation = _accumulator(_accumulation!, value); + } + catch (Exception exception) + { + _accumulation = default; + ForwardOnError(exception); + } + } + + public override void OnError(Exception error) + { + _accumulation = default; + ForwardOnError(error); + } + + public override void OnCompleted() + { + var accumulation = _accumulation!; + _accumulation = default; + + TResult result; + try + { + result = _resultSelector(accumulation); + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + ForwardOnNext(result); + ForwardOnCompleted(); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/All.cs b/LibExternal/System.Reactive/Linq/Observable/All.cs new file mode 100644 index 0000000..f00ac57 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/All.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class All : Producer._> + { + private readonly IObservable _source; + private readonly Func _predicate; + + public All(IObservable source, Func predicate) + { + _source = source; + _predicate = predicate; + } + + protected override _ CreateSink(IObserver observer) => new(_predicate, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private readonly Func _predicate; + + public _(Func predicate, IObserver observer) + : base(observer) + { + _predicate = predicate; + } + + public override void OnNext(TSource value) + { + bool res; + try + { + res = _predicate(value); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + if (!res) + { + ForwardOnNext(false); + ForwardOnCompleted(); + } + } + + public override void OnCompleted() + { + ForwardOnNext(true); + ForwardOnCompleted(); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Amb.cs b/LibExternal/System.Reactive/Linq/Observable/Amb.cs new file mode 100644 index 0000000..b91ac71 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Amb.cs @@ -0,0 +1,136 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Threading; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Amb : Producer.AmbCoordinator> + { + private readonly IObservable _left; + private readonly IObservable _right; + + public Amb(IObservable left, IObservable right) + { + _left = left; + _right = right; + } + + protected override AmbCoordinator CreateSink(IObserver observer) => new(observer); + + protected override void Run(AmbCoordinator sink) => sink.Run(_left, _right); + + internal sealed class AmbCoordinator : IDisposable + { + private readonly AmbObserver _leftObserver; + private readonly AmbObserver _rightObserver; + private int _winner; + + public AmbCoordinator(IObserver observer) + { + _leftObserver = new AmbObserver(observer, this, true); + _rightObserver = new AmbObserver(observer, this, false); + } + + public void Run(IObservable left, IObservable right) + { + _leftObserver.Run(left); + _rightObserver.Run(right); + } + + public void Dispose() + { + _leftObserver.Dispose(); + _rightObserver.Dispose(); + } + + /// + /// Try winning the race for the right of emission. + /// + /// If true, the contender is the left source. + /// True if the contender has won the race. + public bool TryWin(bool isLeft) + { + var index = isLeft ? 1 : 2; + + if (Volatile.Read(ref _winner) == index) + { + return true; + } + if (Interlocked.CompareExchange(ref _winner, index, 0) == 0) + { + (isLeft ? _rightObserver : _leftObserver).Dispose(); + return true; + } + return false; + } + + private sealed class AmbObserver : IdentitySink + { + private readonly AmbCoordinator _parent; + private readonly bool _isLeft; + + /// + /// If true, this observer won the race and now can emit + /// on a fast path. + /// + private bool _iwon; + + public AmbObserver(IObserver downstream, AmbCoordinator parent, bool isLeft) : base(downstream) + { + _parent = parent; + _isLeft = isLeft; + } + + public override void OnCompleted() + { + if (_iwon) + { + ForwardOnCompleted(); + } + else if (_parent.TryWin(_isLeft)) + { + _iwon = true; + ForwardOnCompleted(); + } + else + { + Dispose(); + } + } + + public override void OnError(Exception error) + { + if (_iwon) + { + ForwardOnError(error); + } + else if (_parent.TryWin(_isLeft)) + { + _iwon = true; + ForwardOnError(error); + } + else + { + Dispose(); + } + } + + public override void OnNext(TSource value) + { + if (_iwon) + { + ForwardOnNext(value); + } + else + if (_parent.TryWin(_isLeft)) + { + _iwon = true; + ForwardOnNext(value); + } + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/AmbMany.cs b/LibExternal/System.Reactive/Linq/Observable/AmbMany.cs new file mode 100644 index 0000000..18c1e96 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/AmbMany.cs @@ -0,0 +1,203 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Disposables; +using System.Threading; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class AmbManyArray : BasicProducer + { + private readonly IObservable[] _sources; + + public AmbManyArray(IObservable[] sources) + { + _sources = sources; + } + + protected override IDisposable Run(IObserver observer) + { + return AmbCoordinator.Create(observer, _sources); + } + } + + internal sealed class AmbManyEnumerable : BasicProducer + { + private readonly IEnumerable> _sources; + + public AmbManyEnumerable(IEnumerable> sources) + { + _sources = sources; + } + + protected override IDisposable Run(IObserver observer) + { + var sourcesEnumerable = _sources; + + IObservable[] sources; + try + { + sources = sourcesEnumerable.ToArray(); + } + catch (Exception ex) + { + observer.OnError(ex); + return Disposable.Empty; + } + + return AmbCoordinator.Create(observer, sources); + } + } + + internal sealed class AmbCoordinator : IDisposable + { + private readonly IObserver _downstream; + private readonly InnerObserver?[] _observers; + private int _winner; + + internal AmbCoordinator(IObserver downstream, int n) + { + _downstream = downstream; + + var o = new InnerObserver?[n]; + + for (var i = 0; i < n; i++) + { + o[i] = new InnerObserver(this, i); + } + + _observers = o; + + Volatile.Write(ref _winner, -1); + } + + internal static IDisposable Create(IObserver observer, IObservable[] sources) + { + var n = sources.Length; + if (n == 0) + { + observer.OnCompleted(); + return Disposable.Empty; + } + + if (n == 1) + { + return sources[0].Subscribe(observer); + } + + var parent = new AmbCoordinator(observer, n); + + parent.Subscribe(sources); + + return parent; + } + + internal void Subscribe(IObservable[] sources) + { + for (var i = 0; i < _observers.Length; i++) + { + var inner = Volatile.Read(ref _observers[i]); + + if (inner == null) + { + break; + } + + inner.Run(sources[i]); + } + } + + public void Dispose() + { + for (var i = 0; i < _observers.Length; i++) + { + Interlocked.Exchange(ref _observers[i], null)?.Dispose(); + } + } + + private bool TryWin(int index) + { + if (Volatile.Read(ref _winner) == -1 && Interlocked.CompareExchange(ref _winner, index, -1) == -1) + { + for (var i = 0; i < _observers.Length; i++) + { + if (index != i) + { + Interlocked.Exchange(ref _observers[i], null)?.Dispose(); + } + } + + return true; + } + + return false; + } + + internal sealed class InnerObserver : IdentitySink + { + private readonly AmbCoordinator _parent; + private readonly int _index; + private bool _won; + + public InnerObserver(AmbCoordinator parent, int index) : base(parent._downstream) + { + _parent = parent; + _index = index; + } + + public override void OnCompleted() + { + if (_won) + { + ForwardOnCompleted(); + } + else if (_parent.TryWin(_index)) + { + _won = true; + ForwardOnCompleted(); + } + else + { + Dispose(); + } + } + + public override void OnError(Exception error) + { + if (_won) + { + ForwardOnError(error); + } + else if (_parent.TryWin(_index)) + { + _won = true; + ForwardOnError(error); + } + else + { + Dispose(); + } + } + + public override void OnNext(T value) + { + if (_won) + { + ForwardOnNext(value); + } + else if (_parent.TryWin(_index)) + { + _won = true; + ForwardOnNext(value); + } + else + { + Dispose(); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Any.cs b/LibExternal/System.Reactive/Linq/Observable/Any.cs new file mode 100644 index 0000000..7384972 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Any.cs @@ -0,0 +1,96 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class Any + { + internal sealed class Count : Producer + { + private readonly IObservable _source; + + public Count(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(TSource value) + { + ForwardOnNext(true); + ForwardOnCompleted(); + } + + public override void OnCompleted() + { + ForwardOnNext(false); + ForwardOnCompleted(); + } + } + } + + internal sealed class Predicate : Producer + { + private readonly IObservable _source; + private readonly Func _predicate; + + public Predicate(IObservable source, Func predicate) + { + _source = source; + _predicate = predicate; + } + + protected override _ CreateSink(IObserver observer) => new(_predicate, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private readonly Func _predicate; + + public _(Func predicate, IObserver observer) + : base(observer) + { + _predicate = predicate; + } + + public override void OnNext(TSource value) + { + bool res; + try + { + res = _predicate(value); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + if (res) + { + ForwardOnNext(true); + ForwardOnCompleted(); + } + } + + public override void OnCompleted() + { + ForwardOnNext(false); + ForwardOnCompleted(); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/AppendPrepend.cs b/LibExternal/System.Reactive/Linq/Observable/AppendPrepend.cs new file mode 100644 index 0000000..c0de3a1 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/AppendPrepend.cs @@ -0,0 +1,549 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Reactive.Concurrency; +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class AppendPrepend + { + internal interface IAppendPrepend : IObservable + { + IAppendPrepend Append(TSource value); + IAppendPrepend Prepend(TSource value); + IScheduler Scheduler { get; } + } + + internal abstract class SingleBase : Producer, IAppendPrepend + where TSink : IDisposable + { + protected readonly IObservable _source; + protected readonly TSource _value; + protected readonly bool _append; + + public abstract IScheduler Scheduler { get; } + + public SingleBase(IObservable source, TSource value, bool append) + { + _source = source; + _value = value; + _append = append; + } + + public IAppendPrepend Append(TSource value) + { + var prev = new Node(_value); + + Node appendNode; + Node? prependNode = null; + + if (_append) + { + appendNode = new Node(prev, value); + } + else + { + prependNode = prev; + appendNode = new Node(value); + } + + return CreateAppendPrepend(prependNode, appendNode); + } + + public IAppendPrepend Prepend(TSource value) + { + var prev = new Node(_value); + + Node? appendNode = null; + Node prependNode; + + if (_append) + { + prependNode = new Node(value); + appendNode = prev; + } + else + { + prependNode = new Node(prev, value); + } + + return CreateAppendPrepend(prependNode, appendNode); + } + + private IAppendPrepend CreateAppendPrepend(Node? prepend, Node? append) + { + if (Scheduler is ISchedulerLongRunning longRunning) + { + return new LongRunning(_source, prepend, append, Scheduler, longRunning); + } + + return new Recursive(_source, prepend, append, Scheduler); + } + } + + + internal sealed class SingleValue : SingleBase + { + public override IScheduler Scheduler { get; } + + public SingleValue(IObservable source, TSource value, IScheduler scheduler, bool append) + : base (source, value, append) + { + Scheduler = scheduler; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(); + + internal sealed class _ : IdentitySink + { + private readonly IObservable _source; + private readonly TSource _value; + private readonly IScheduler _scheduler; + private readonly bool _append; + private SingleAssignmentDisposableValue _schedulerDisposable; + + public _(SingleValue parent, IObserver observer) + : base(observer) + { + _source = parent._source; + _value = parent._value; + _scheduler = parent.Scheduler; + _append = parent._append; + } + + public void Run() + { + var disp = _append + ? _source.SubscribeSafe(this) + : _scheduler.ScheduleAction(this, PrependValue); + + SetUpstream(disp); + } + + private static IDisposable PrependValue(_ sink) + { + sink.ForwardOnNext(sink._value); + return sink._source.SubscribeSafe(sink); + } + + public override void OnCompleted() + { + if (_append) + { + var disposable = _scheduler.ScheduleAction(this, AppendValue); + _schedulerDisposable.Disposable = disposable; + } + else + { + ForwardOnCompleted(); + } + } + + private static void AppendValue(_ sink) + { + sink.ForwardOnNext(sink._value); + sink.ForwardOnCompleted(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _schedulerDisposable.Dispose(); + } + base.Dispose(disposing); + } + } + } + + private sealed class Recursive : Producer, IAppendPrepend + { + private readonly IObservable _source; + private readonly Node? _appends; + private readonly Node? _prepends; + + public IScheduler Scheduler { get; } + + public Recursive(IObservable source, Node? prepends, Node? appends, IScheduler scheduler) + { + _source = source; + _appends = appends; + _prepends = prepends; + Scheduler = scheduler; + } + + public IAppendPrepend Append(TSource value) + { + return new Recursive(_source, + _prepends, new Node(_appends, value), Scheduler); + } + + public IAppendPrepend Prepend(TSource value) + { + return new Recursive(_source, + new Node(_prepends, value), _appends, Scheduler); + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(); + + // The sink is based on the sink of the ToObervalbe class and does basically + // the same twice, once for the append list and once for the prepend list. + // Inbetween it forwards the values of the source class. + // + internal sealed class _ : IdentitySink + { + private readonly IObservable _source; + private readonly Node? _appends; + private readonly IScheduler _scheduler; + + private Node? _currentPrependNode; + private TSource[]? _appendArray; + private int _currentAppendIndex; + private volatile bool _disposed; + + public _(Recursive parent, IObserver observer) + : base(observer) + { + _source = parent._source; + _scheduler = parent.Scheduler; + _currentPrependNode = parent._prepends; + _appends = parent._appends; + } + + public void Run() + { + if (_currentPrependNode == null) + { + SetUpstream(_source.SubscribeSafe(this)); + } + else + { + // + // We never allow the scheduled work to be cancelled. Instead, the _disposed flag + // is used to have PrependValues() bail out. + // + _scheduler.Schedule(this, static (innerScheduler, @this) => @this.PrependValues(innerScheduler)); + } + } + + public override void OnCompleted() + { + if (_appends == null) + { + ForwardOnCompleted(); + } + else + { + _appendArray = _appends.ToReverseArray(); + + // + // We never allow the scheduled work to be cancelled. Instead, the _disposed flag + // is used to have `AppendValues` bail out. + // + _scheduler.Schedule(this, static (innerScheduler, @this) => @this.AppendValues(innerScheduler)); + } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _disposed = true; + } + + base.Dispose(disposing); + } + + private IDisposable PrependValues(IScheduler scheduler) + { + if (_disposed) + { + return Disposable.Empty; + } + + Debug.Assert(_currentPrependNode != null); + + var current = _currentPrependNode!.Value; + ForwardOnNext(current); + + _currentPrependNode = _currentPrependNode.Parent; + if (_currentPrependNode == null) + { + SetUpstream(_source.SubscribeSafe(this)); + } + else + { + // + // We never allow the scheduled work to be cancelled. Instead, the _disposed flag + // is used to have PrependValues() bail out. + // + scheduler.Schedule(this, static (innerScheduler, @this) => @this.PrependValues(innerScheduler)); + } + + return Disposable.Empty; + } + + private IDisposable AppendValues(IScheduler scheduler) + { + if (_disposed) + { + return Disposable.Empty; + } + + Debug.Assert(_appendArray != null); + + var current = _appendArray![_currentAppendIndex]; + ForwardOnNext(current); + + _currentAppendIndex++; + + if (_currentAppendIndex == _appendArray.Length) + { + ForwardOnCompleted(); + } + else + { + // + // We never allow the scheduled work to be cancelled. Instead, the _disposed flag + // is used to have AppendValues() bail out. + // + scheduler.Schedule(this, static (innerScheduler, @this) => @this.AppendValues(innerScheduler)); + } + + return Disposable.Empty; + } + } + } + + private sealed class LongRunning : Producer, IAppendPrepend + { + private readonly IObservable _source; + private readonly Node? _appends; + private readonly Node? _prepends; + private readonly ISchedulerLongRunning _longRunningScheduler; + + public IScheduler Scheduler { get; } + + public LongRunning(IObservable source, Node? prepends, Node? appends, IScheduler scheduler, ISchedulerLongRunning longRunningScheduler) + { + _source = source; + _appends = appends; + _prepends = prepends; + Scheduler = scheduler; + _longRunningScheduler = longRunningScheduler; + } + + public IAppendPrepend Append(TSource value) + { + return new LongRunning(_source, + _prepends, new Node(_appends, value), Scheduler, _longRunningScheduler); + } + + public IAppendPrepend Prepend(TSource value) + { + return new LongRunning(_source, + new Node(_prepends, value), _appends, Scheduler, _longRunningScheduler); + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(); + + // The sink is based on the sink of the ToObervalbe class and does basically + // the same twice, once for the append list and once for the prepend list. + // Inbetween it forwards the values of the source class. + // + internal sealed class _ : IdentitySink + { + private readonly IObservable _source; + private readonly Node? _prepends; + private readonly Node? _appends; + private readonly ISchedulerLongRunning _scheduler; + + private SerialDisposableValue _schedulerDisposable; + + public _(LongRunning parent, IObserver observer) + : base(observer) + { + _source = parent._source; + _scheduler = parent._longRunningScheduler; + _prepends = parent._prepends; + _appends = parent._appends; + } + + public void Run() + { + if (_prepends == null) + { + SetUpstream(_source.SubscribeSafe(this)); + } + else + { + var disposable = _scheduler.ScheduleLongRunning(this, static (@this, cancel) => @this.PrependValues(cancel)); + _schedulerDisposable.TrySetFirst(disposable); + } + } + + public override void OnCompleted() + { + if (_appends == null) + { + ForwardOnCompleted(); + } + else + { + var disposable = _scheduler.ScheduleLongRunning(this, static (@this, cancel) => @this.AppendValues(cancel)); + _schedulerDisposable.Disposable = disposable; + } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _schedulerDisposable.Dispose(); + } + + base.Dispose(disposing); + } + + private void PrependValues(ICancelable cancel) + { + Debug.Assert(_prepends != null); + + var current = _prepends!; + + while (!cancel.IsDisposed) + { + ForwardOnNext(current.Value); + current = current.Parent; + + if (current == null) + { + SetUpstream(_source.SubscribeSafe(this)); + break; + } + } + } + + private void AppendValues(ICancelable cancel) + { + Debug.Assert(_appends != null); + + var array = _appends!.ToReverseArray(); + var i = 0; + + while (!cancel.IsDisposed) + { + ForwardOnNext(array[i]); + i++; + + if (i == array.Length) + { + ForwardOnCompleted(); + break; + } + } + } + } + } + + private sealed class Node + { + public readonly Node? Parent; + public readonly T Value; + public readonly int Count; + + public Node(T value) + : this(null, value) + { + } + + public Node(Node? parent, T value) + { + Parent = parent; + Value = value; + + if (parent == null) + { + Count = 1; + } + else + { + if (parent.Count == int.MaxValue) + { + throw new NotSupportedException($"Consecutive appends or prepends with a count of more than int.MaxValue ({int.MaxValue}) are not supported."); + } + + Count = parent.Count + 1; + } + } + + public T[] ToReverseArray() + { + var array = new T[Count]; + var current = this; + for (var i = Count - 1; i >= 0; i--) + { + array[i] = current!.Value; // NB: Count property ensures non-nullability. + current = current.Parent; + } + return array; + } + } + + internal sealed class SingleImmediate : SingleBase + { + public override IScheduler Scheduler => ImmediateScheduler.Instance; + + public SingleImmediate(IObservable source, TSource value, bool append) + : base(source, value, append) + { + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(); + + internal sealed class _ : IdentitySink + { + private readonly IObservable _source; + private readonly TSource _value; + private readonly bool _append; + + public _(SingleImmediate parent, IObserver observer) + : base(observer) + { + _source = parent._source; + _value = parent._value; + _append = parent._append; + } + + public void Run() + { + if (!_append) + { + ForwardOnNext(_value); + } + Run(_source); + } + + public override void OnCompleted() + { + if (_append) + { + ForwardOnNext(_value); + } + ForwardOnCompleted(); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/AsObservable.cs b/LibExternal/System.Reactive/Linq/Observable/AsObservable.cs new file mode 100644 index 0000000..75c6bff --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/AsObservable.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class AsObservable : Producer._>, IEvaluatableObservable + { + private readonly IObservable _source; + + public AsObservable(IObservable source) + { + _source = source; + } + + public IObservable Eval() => _source; + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + public _(IObserver observer) + : base(observer) + { + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/AutoConnect.cs b/LibExternal/System.Reactive/Linq/Observable/AutoConnect.cs new file mode 100644 index 0000000..71189cf --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/AutoConnect.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Subjects; +using System.Threading; + +namespace System.Reactive.Linq.ObservableImpl +{ + /// + /// Automatically connect the upstream IConnectableObservable once the + /// specified number of IObservers have subscribed to this IObservable. + /// + /// The upstream value type. + internal sealed class AutoConnect : IObservable + { + private readonly IConnectableObservable _source; + private readonly int _minObservers; + private readonly Action? _onConnect; + private int _count; + + internal AutoConnect(IConnectableObservable source, int minObservers, Action? onConnect) + { + _source = source; + _minObservers = minObservers; + _onConnect = onConnect; + } + + public IDisposable Subscribe(IObserver observer) + { + var d = _source.Subscribe(observer); + + if (Volatile.Read(ref _count) < _minObservers) + { + if (Interlocked.Increment(ref _count) == _minObservers) + { + var c = _source.Connect(); + _onConnect?.Invoke(c); + } + } + return d; + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Average.cs b/LibExternal/System.Reactive/Linq/Observable/Average.cs new file mode 100644 index 0000000..9767f36 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Average.cs @@ -0,0 +1,621 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class AverageDouble : Producer + { + private readonly IObservable _source; + + public AverageDouble(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private double _sum; + private long _count; + + public _(IObserver observer) + : base(observer) + { + _sum = 0.0; + _count = 0L; + } + + public override void OnNext(double value) + { + try + { + checked + { + _sum += value; + _count++; + } + } + catch (Exception ex) + { + ForwardOnError(ex); + } + } + + public override void OnCompleted() + { + if (_count > 0) + { + ForwardOnNext(_sum / _count); + ForwardOnCompleted(); + } + else + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + } + } + } + + internal sealed class AverageSingle : Producer + { + private readonly IObservable _source; + + public AverageSingle(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private double _sum; // NOTE: Uses a different accumulator type (double), conform LINQ to Objects. + private long _count; + + public _(IObserver observer) + : base(observer) + { + _sum = 0.0; + _count = 0L; + } + + public override void OnNext(float value) + { + try + { + checked + { + _sum += value; + _count++; + } + } + catch (Exception ex) + { + ForwardOnError(ex); + } + } + + public override void OnCompleted() + { + if (_count > 0) + { + ForwardOnNext((float)(_sum / _count)); + ForwardOnCompleted(); + } + else + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + } + } + } + + internal sealed class AverageDecimal : Producer + { + private readonly IObservable _source; + + public AverageDecimal(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private decimal _sum; + private long _count; + + public _(IObserver observer) + : base(observer) + { + _sum = 0M; + _count = 0L; + } + + public override void OnNext(decimal value) + { + try + { + checked + { + _sum += value; + _count++; + } + } + catch (Exception ex) + { + ForwardOnError(ex); + } + } + + public override void OnCompleted() + { + if (_count > 0) + { + ForwardOnNext(_sum / _count); + ForwardOnCompleted(); + } + else + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + } + } + } + + internal sealed class AverageInt32 : Producer + { + private readonly IObservable _source; + + public AverageInt32(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private long _sum; + private long _count; + + public _(IObserver observer) + : base(observer) + { + _sum = 0L; + _count = 0L; + } + + public override void OnNext(int value) + { + try + { + checked + { + _sum += value; + _count++; + } + } + catch (Exception ex) + { + ForwardOnError(ex); + } + } + + public override void OnCompleted() + { + if (_count > 0) + { + ForwardOnNext((double)_sum / _count); + ForwardOnCompleted(); + } + else + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + } + } + } + + internal sealed class AverageInt64 : Producer + { + private readonly IObservable _source; + + public AverageInt64(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private long _sum; + private long _count; + + public _(IObserver observer) + : base(observer) + { + _sum = 0L; + _count = 0L; + } + + public override void OnNext(long value) + { + try + { + checked + { + _sum += value; + _count++; + } + } + catch (Exception ex) + { + ForwardOnError(ex); + } + } + + public override void OnCompleted() + { + if (_count > 0) + { + ForwardOnNext((double)_sum / _count); + ForwardOnCompleted(); + } + else + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + } + } + } + + internal sealed class AverageDoubleNullable : Producer + { + private readonly IObservable _source; + + public AverageDoubleNullable(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private double _sum; + private long _count; + + public _(IObserver observer) + : base(observer) + { + _sum = 0.0; + _count = 0L; + } + + public override void OnNext(double? value) + { + try + { + checked + { + if (value != null) + { + _sum += value.Value; + _count++; + } + } + } + catch (Exception ex) + { + ForwardOnError(ex); + } + } + + public override void OnCompleted() + { + if (_count > 0) + { + ForwardOnNext(_sum / _count); + } + else + { + ForwardOnNext(null); + } + + ForwardOnCompleted(); + } + } + } + + internal sealed class AverageSingleNullable : Producer + { + private readonly IObservable _source; + + public AverageSingleNullable(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private double _sum; // NOTE: Uses a different accumulator type (double), conform LINQ to Objects. + private long _count; + + public _(IObserver observer) + : base(observer) + { + _sum = 0.0; + _count = 0L; + } + + public override void OnNext(float? value) + { + try + { + checked + { + if (value != null) + { + _sum += value.Value; + _count++; + } + } + } + catch (Exception ex) + { + ForwardOnError(ex); + } + } + + public override void OnCompleted() + { + if (_count > 0) + { + ForwardOnNext((float)(_sum / _count)); + } + else + { + ForwardOnNext(null); + } + + ForwardOnCompleted(); + } + } + } + + internal sealed class AverageDecimalNullable : Producer + { + private readonly IObservable _source; + + public AverageDecimalNullable(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private decimal _sum; + private long _count; + + public _(IObserver observer) + : base(observer) + { + _sum = 0M; + _count = 0L; + } + + public override void OnNext(decimal? value) + { + try + { + checked + { + if (value != null) + { + _sum += value.Value; + _count++; + } + } + } + catch (Exception ex) + { + ForwardOnError(ex); + } + } + + public override void OnCompleted() + { + if (_count > 0) + { + ForwardOnNext(_sum / _count); + } + else + { + ForwardOnNext(null); + } + + ForwardOnCompleted(); + } + } + } + + internal sealed class AverageInt32Nullable : Producer + { + private readonly IObservable _source; + + public AverageInt32Nullable(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private long _sum; + private long _count; + + public _(IObserver observer) + : base(observer) + { + _sum = 0L; + _count = 0L; + } + + public override void OnNext(int? value) + { + try + { + checked + { + if (value != null) + { + _sum += value.Value; + _count++; + } + } + } + catch (Exception ex) + { + ForwardOnError(ex); + } + } + + public override void OnCompleted() + { + if (_count > 0) + { + ForwardOnNext((double)_sum / _count); + } + else + { + ForwardOnNext(null); + } + + ForwardOnCompleted(); + } + } + } + + internal sealed class AverageInt64Nullable : Producer + { + private readonly IObservable _source; + + public AverageInt64Nullable(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private long _sum; + private long _count; + + public _(IObserver observer) + : base(observer) + { + _sum = 0L; + _count = 0L; + } + + public override void OnNext(long? value) + { + try + { + checked + { + if (value != null) + { + _sum += value.Value; + _count++; + } + } + } + catch (Exception ex) + { + ForwardOnError(ex); + } + } + + public override void OnCompleted() + { + if (_count > 0) + { + ForwardOnNext((double)_sum / _count); + } + else + { + ForwardOnNext(null); + } + + ForwardOnCompleted(); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Buffer.cs b/LibExternal/System.Reactive/Linq/Observable/Buffer.cs new file mode 100644 index 0000000..c74db99 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Buffer.cs @@ -0,0 +1,870 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Concurrency; +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class Buffer + { + internal sealed class CountExact : Producer, CountExact.ExactSink> + { + private readonly IObservable _source; + private readonly int _count; + + public CountExact(IObservable source, int count) + { + _source = source; + _count = count; + } + + protected override ExactSink CreateSink(IObserver> observer) => new(observer, _count); + + protected override void Run(ExactSink sink) => sink.Run(_source); + + internal sealed class ExactSink : Sink> + { + private readonly int _count; + private int _index; + private IList? _buffer; + + internal ExactSink(IObserver> observer, int count) : base(observer) + { + _count = count; + } + + public override void OnNext(TSource value) + { + var buffer = _buffer; + if (buffer == null) + { + buffer = new List(); + _buffer = buffer; + } + + buffer.Add(value); + + var idx = _index + 1; + if (idx == _count) + { + _buffer = null; + _index = 0; + ForwardOnNext(buffer); + } + else + { + _index = idx; + } + } + + public override void OnError(Exception error) + { + _buffer = null; + ForwardOnError(error); + } + + public override void OnCompleted() + { + var buffer = _buffer; + _buffer = null; + + if (buffer != null) + { + ForwardOnNext(buffer); + } + ForwardOnCompleted(); + } + } + } + + internal sealed class CountSkip : Producer, CountSkip.SkipSink> + { + private readonly IObservable _source; + private readonly int _count; + private readonly int _skip; + + public CountSkip(IObservable source, int count, int skip) + { + _source = source; + _count = count; + _skip = skip; + } + + protected override SkipSink CreateSink(IObserver> observer) => new(observer, _count, _skip); + + protected override void Run(SkipSink sink) => sink.Run(_source); + + internal sealed class SkipSink : Sink> + { + private readonly int _count; + private readonly int _skip; + private int _index; + private IList? _buffer; + + internal SkipSink(IObserver> observer, int count, int skip) : base(observer) + { + _count = count; + _skip = skip; + } + + public override void OnNext(TSource value) + { + var idx = _index; + var buffer = _buffer; + if (idx == 0) + { + buffer = new List(); + _buffer = buffer; + } + + buffer?.Add(value); + + if (++idx == _count) + { + _buffer = null; + ForwardOnNext(buffer!); // NB: Counting logic with _index ensures non-null. + } + + if (idx == _skip) + { + _index = 0; + } + else + { + _index = idx; + } + } + + public override void OnError(Exception error) + { + _buffer = null; + ForwardOnError(error); + } + + public override void OnCompleted() + { + var buffer = _buffer; + _buffer = null; + + if (buffer != null) + { + ForwardOnNext(buffer); + } + ForwardOnCompleted(); + } + } + } + + internal sealed class CountOverlap : Producer, CountOverlap.OverlapSink> + { + private readonly IObservable _source; + private readonly int _count; + private readonly int _skip; + + public CountOverlap(IObservable source, int count, int skip) + { + _source = source; + _count = count; + _skip = skip; + } + + protected override OverlapSink CreateSink(IObserver> observer) => new(observer, _count, _skip); + + protected override void Run(OverlapSink sink) => sink.Run(_source); + + internal sealed class OverlapSink : Sink> + { + private readonly Queue> _queue; + private readonly int _count; + private readonly int _skip; + private int _n; + + public OverlapSink(IObserver> observer, int count, int skip) + : base(observer) + { + _queue = new Queue>(); + _count = count; + _skip = skip; + CreateWindow(); + } + + private void CreateWindow() + { + var s = new List(); + _queue.Enqueue(s); + } + + public override void OnNext(TSource value) + { + foreach (var s in _queue) + { + s.Add(value); + } + + var c = _n - _count + 1; + if (c >= 0 && c % _skip == 0) + { + var s = _queue.Dequeue(); + if (s.Count > 0) + { + ForwardOnNext(s); + } + } + + _n++; + if (_n % _skip == 0) + { + CreateWindow(); + } + } + + public override void OnError(Exception error) + { + // just drop the ILists on the GC floor, no reason to clear them + _queue.Clear(); + + ForwardOnError(error); + } + + public override void OnCompleted() + { + while (_queue.Count > 0) + { + var s = _queue.Dequeue(); + if (s.Count > 0) + { + ForwardOnNext(s); + } + } + + ForwardOnCompleted(); + } + } + } + + internal sealed class TimeSliding : Producer, TimeSliding._> + { + private readonly IObservable _source; + private readonly TimeSpan _timeSpan; + private readonly TimeSpan _timeShift; + private readonly IScheduler _scheduler; + + public TimeSliding(IObservable source, TimeSpan timeSpan, TimeSpan timeShift, IScheduler scheduler) + { + _source = source; + _timeSpan = timeSpan; + _timeShift = timeShift; + _scheduler = scheduler; + } + + protected override _ CreateSink(IObserver> observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : Sink> + { + private readonly TimeSpan _timeShift; + private readonly IScheduler _scheduler; + private readonly object _gate = new(); + private readonly Queue> _q = new(); + private SerialDisposableValue _timerSerial; + + public _(TimeSliding parent, IObserver> observer) + : base(observer) + { + _timeShift = parent._timeShift; + _scheduler = parent._scheduler; + } + + private TimeSpan _totalTime; + private TimeSpan _nextShift; + private TimeSpan _nextSpan; + + public void Run(TimeSliding parent) + { + _totalTime = TimeSpan.Zero; + _nextShift = parent._timeShift; + _nextSpan = parent._timeSpan; + + CreateWindow(); + CreateTimer(); + + Run(parent._source); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _timerSerial.Dispose(); + } + base.Dispose(disposing); + } + + private void CreateWindow() + { + var s = new List(); + _q.Enqueue(s); + } + + private void CreateTimer() + { + var m = new SingleAssignmentDisposable(); + + _timerSerial.Disposable = m; + + var isSpan = false; + var isShift = false; + if (_nextSpan == _nextShift) + { + isSpan = true; + isShift = true; + } + else if (_nextSpan < _nextShift) + { + isSpan = true; + } + else + { + isShift = true; + } + + var newTotalTime = isSpan ? _nextSpan : _nextShift; + var ts = newTotalTime - _totalTime; + _totalTime = newTotalTime; + + if (isSpan) + { + _nextSpan += _timeShift; + } + + if (isShift) + { + _nextShift += _timeShift; + } + + m.Disposable = _scheduler.ScheduleAction((@this: this, isSpan, isShift), ts, static tuple => tuple.@this.Tick(tuple.isSpan, tuple.isShift)); + } + + private void Tick(bool isSpan, bool isShift) + { + lock (_gate) + { + // + // Before v2, the two operations below were reversed. This doesn't have an observable + // difference for Buffer, but is done to keep code consistent with Window, where we + // took a breaking change in v2 to ensure consistency across overloads. For more info, + // see the comment in Tick for Window. + // + if (isSpan) + { + if (_q.Count > 0) + { + var s = _q.Dequeue(); + ForwardOnNext(s); + } + } + + if (isShift) + { + CreateWindow(); + } + } + + CreateTimer(); + } + + public override void OnNext(TSource value) + { + lock (_gate) + { + foreach (var s in _q) + { + s.Add(value); + } + } + } + + public override void OnError(Exception error) + { + lock (_gate) + { + while (_q.Count > 0) + { + _q.Dequeue().Clear(); + } + + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + lock (_gate) + { + while (_q.Count > 0) + { + ForwardOnNext(_q.Dequeue()); + } + + ForwardOnCompleted(); + } + } + } + } + + internal sealed class TimeHopping : Producer, TimeHopping._> + { + private readonly IObservable _source; + private readonly TimeSpan _timeSpan; + private readonly IScheduler _scheduler; + + public TimeHopping(IObservable source, TimeSpan timeSpan, IScheduler scheduler) + { + _source = source; + _timeSpan = timeSpan; + _scheduler = scheduler; + } + + protected override _ CreateSink(IObserver> observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : Sink> + { + private readonly object _gate = new(); + private List _list = new(); + + public _(IObserver> observer) + : base(observer) + { + } + + private SingleAssignmentDisposableValue _periodicDisposable; + + public void Run(TimeHopping parent) + { + _periodicDisposable.Disposable = parent._scheduler.SchedulePeriodic(this, parent._timeSpan, static @this => @this.Tick()); + Run(parent._source); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _periodicDisposable.Dispose(); + } + base.Dispose(disposing); + } + + private void Tick() + { + lock (_gate) + { + ForwardOnNext(_list); + _list = new List(); + } + } + + public override void OnNext(TSource value) + { + lock (_gate) + { + _list.Add(value); + } + } + + public override void OnError(Exception error) + { + lock (_gate) + { + _list.Clear(); + + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + lock (_gate) + { + ForwardOnNext(_list); + ForwardOnCompleted(); + } + } + } + } + + internal sealed class Ferry : Producer, Ferry._> + { + private readonly IObservable _source; + private readonly int _count; + private readonly TimeSpan _timeSpan; + private readonly IScheduler _scheduler; + + public Ferry(IObservable source, TimeSpan timeSpan, int count, IScheduler scheduler) + { + _source = source; + _timeSpan = timeSpan; + _count = count; + _scheduler = scheduler; + } + + protected override _ CreateSink(IObserver> observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(); + + internal sealed class _ : Sink> + { + private readonly Ferry _parent; + private readonly object _gate = new(); + private List _s = new(); + + public _(Ferry parent, IObserver> observer) + : base(observer) + { + _parent = parent; + } + + private SerialDisposableValue _timerSerial; + private int _n; + private int _windowId; + + public void Run() + { + _n = 0; + _windowId = 0; + + CreateTimer(0); + + SetUpstream(_parent._source.SubscribeSafe(this)); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _timerSerial.Dispose(); + } + + base.Dispose(disposing); + } + + private void CreateTimer(int id) + { + var m = new SingleAssignmentDisposable(); + _timerSerial.Disposable = m; + + m.Disposable = _parent._scheduler.ScheduleAction((@this: this, id), _parent._timeSpan, static tuple => tuple.@this.Tick(tuple.id)); + } + + private void Tick(int id) + { + lock (_gate) + { + if (id != _windowId) + { + return; + } + + _n = 0; + var newId = ++_windowId; + + var res = _s; + _s = new List(); + ForwardOnNext(res); + + CreateTimer(newId); + } + } + + public override void OnNext(TSource value) + { + var newWindow = false; + var newId = 0; + + lock (_gate) + { + _s.Add(value); + + _n++; + if (_n == _parent._count) + { + newWindow = true; + _n = 0; + newId = ++_windowId; + + var res = _s; + _s = new List(); + ForwardOnNext(res); + } + + if (newWindow) + { + CreateTimer(newId); + } + } + } + + public override void OnError(Exception error) + { + lock (_gate) + { + _s.Clear(); + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + lock (_gate) + { + ForwardOnNext(_s); + ForwardOnCompleted(); + } + } + } + } + } + + internal static class Buffer + { + internal sealed class Selector : Producer, Selector._> + { + private readonly IObservable _source; + private readonly Func> _bufferClosingSelector; + + public Selector(IObservable source, Func> bufferClosingSelector) + { + _source = source; + _bufferClosingSelector = bufferClosingSelector; + } + + protected override _ CreateSink(IObserver> observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink> + { + private readonly object _gate = new(); + private readonly AsyncLock _bufferGate = new(); + private readonly Func> _bufferClosingSelector; + + private List _buffer = new(); + private SerialDisposableValue _bufferClosingSerialDisposable; + + public _(Selector parent, IObserver> observer) + : base(observer) + { + _bufferClosingSelector = parent._bufferClosingSelector; + } + + public override void Run(IObservable source) + { + base.Run(source); + + _bufferGate.Wait(this, static @this => @this.CreateBufferClose()); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _bufferClosingSerialDisposable.Dispose(); + } + base.Dispose(disposing); + } + + private void CreateBufferClose() + { + IObservable bufferClose; + try + { + bufferClose = _bufferClosingSelector(); + } + catch (Exception exception) + { + lock (_gate) + { + ForwardOnError(exception); + } + return; + } + + var closingObserver = new BufferClosingObserver(this); + _bufferClosingSerialDisposable.Disposable = closingObserver; + closingObserver.SetResource(bufferClose.SubscribeSafe(closingObserver)); + } + + private void CloseBuffer(IDisposable closingSubscription) + { + closingSubscription.Dispose(); + + lock (_gate) + { + var res = _buffer; + _buffer = new List(); + ForwardOnNext(res); + } + + _bufferGate.Wait(this, static @this => @this.CreateBufferClose()); + } + + private sealed class BufferClosingObserver : SafeObserver + { + private readonly _ _parent; + + public BufferClosingObserver(_ parent) + { + _parent = parent; + } + + public override void OnNext(TBufferClosing value) + { + _parent.CloseBuffer(this); + } + + public override void OnError(Exception error) + { + _parent.OnError(error); + } + + public override void OnCompleted() + { + _parent.CloseBuffer(this); + } + } + + public override void OnNext(TSource value) + { + lock (_gate) + { + _buffer.Add(value); + } + } + + public override void OnError(Exception error) + { + lock (_gate) + { + _buffer.Clear(); + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + lock (_gate) + { + ForwardOnNext(_buffer); + ForwardOnCompleted(); + } + } + } + } + + internal sealed class Boundaries : Producer, Boundaries._> + { + private readonly IObservable _source; + private readonly IObservable _bufferBoundaries; + + public Boundaries(IObservable source, IObservable bufferBoundaries) + { + _source = source; + _bufferBoundaries = bufferBoundaries; + } + + protected override _ CreateSink(IObserver> observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : Sink> + { + private readonly object _gate = new(); + + private List _buffer = new(); + private SingleAssignmentDisposableValue _boundariesDisposable; + + public _(IObserver> observer) + : base(observer) + { + } + + public void Run(Boundaries parent) + { + Run(parent._source); + _boundariesDisposable.Disposable = parent._bufferBoundaries.SubscribeSafe(new BufferClosingObserver(this)); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _boundariesDisposable.Dispose(); + } + + base.Dispose(disposing); + } + + private sealed class BufferClosingObserver : IObserver + { + private readonly _ _parent; + + public BufferClosingObserver(_ parent) + { + _parent = parent; + } + + public void OnNext(TBufferClosing value) + { + lock (_parent._gate) + { + var res = _parent._buffer; + _parent._buffer = new List(); + _parent.ForwardOnNext(res); + } + } + + public void OnError(Exception error) + { + _parent.OnError(error); + } + + public void OnCompleted() + { + _parent.OnCompleted(); + } + } + + public override void OnNext(TSource value) + { + lock (_gate) + { + _buffer.Add(value); + } + } + + public override void OnError(Exception error) + { + lock (_gate) + { + _buffer.Clear(); + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + lock (_gate) + { + ForwardOnNext(_buffer); + ForwardOnCompleted(); + } + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Case.cs b/LibExternal/System.Reactive/Linq/Observable/Case.cs new file mode 100644 index 0000000..431e8e7 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Case.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Case : Producer._>, IEvaluatableObservable + where TValue : notnull + { + private readonly Func _selector; + private readonly IDictionary> _sources; + private readonly IObservable _defaultSource; + + public Case(Func selector, IDictionary> sources, IObservable defaultSource) + { + _selector = selector; + _sources = sources; + _defaultSource = defaultSource; + } + + public IObservable Eval() + { + if (_sources.TryGetValue(_selector(), out var res)) + { + return res; + } + + return _defaultSource; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : IdentitySink + { + public _(IObserver observer) + : base(observer) + { + } + + public void Run(Case parent) + { + IObservable result; + try + { + result = parent.Eval(); + } + catch (Exception exception) + { + ForwardOnError(exception); + + return; + } + + Run(result); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Cast.cs b/LibExternal/System.Reactive/Linq/Observable/Cast.cs new file mode 100644 index 0000000..f15a0ab --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Cast.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Cast : Producer._> /* Could optimize further by deriving from Select and providing Combine. We're not doing this (yet) for debuggability. */ + { + private readonly IObservable _source; + + public Cast(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(TSource value) + { + TResult? result; + try + { + result = (TResult?)(object?)value; + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + ForwardOnNext(result!); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Catch.cs b/LibExternal/System.Reactive/Linq/Observable/Catch.cs new file mode 100644 index 0000000..492fe8a --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Catch.cs @@ -0,0 +1,142 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Disposables; +using System.Threading; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Catch : Producer._> + { + private readonly IEnumerable> _sources; + + public Catch(IEnumerable> sources) + { + _sources = sources; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_sources); + + internal sealed class _ : TailRecursiveSink + { + public _(IObserver observer) + : base(observer) + { + } + + protected override IEnumerable>? Extract(IObservable source) + { + if (source is Catch @catch) + { + return @catch._sources; + } + + return null; + } + + private Exception? _lastException; + + public override void OnError(Exception error) + { + _lastException = error; + Recurse(); + } + + protected override void Done() + { + if (_lastException != null) + { + ForwardOnError(_lastException); + } + else + { + ForwardOnCompleted(); + } + } + + protected override bool Fail(Exception error) + { + // + // Note that the invocation of _recurse in OnError will + // cause the next MoveNext operation to be enqueued, so + // we will still return to the caller immediately. + // + OnError(error); + return true; + } + } + } + + internal sealed class Catch : Producer._> where TException : Exception + { + private readonly IObservable _source; + private readonly Func> _handler; + + public Catch(IObservable source, Func> handler) + { + _source = source; + _handler = handler; + } + + protected override _ CreateSink(IObserver observer) => new(_handler, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly Func> _handler; + + public _(Func> handler, IObserver observer) + : base(observer) + { + _handler = handler; + } + + private bool _once; + private SerialDisposableValue _subscription; + + public override void Run(IObservable source) + { + _subscription.TrySetFirst(source.SubscribeSafe(this)); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _subscription.Dispose(); + } + + base.Dispose(disposing); + } + + public override void OnError(Exception error) + { + if (!Volatile.Read(ref _once) && error is TException e) + { + IObservable result; + try + { + result = _handler(e); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + Volatile.Write(ref _once, true); + _subscription.Disposable = result.SubscribeSafe(this); + } + else + { + ForwardOnError(error); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Collect.cs b/LibExternal/System.Reactive/Linq/Observable/Collect.cs new file mode 100644 index 0000000..8000609 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Collect.cs @@ -0,0 +1,127 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics.CodeAnalysis; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Collect : PushToPullAdapter + { + private readonly Func _getInitialCollector; + private readonly Func _merge; + private readonly Func _getNewCollector; + + public Collect(IObservable source, Func getInitialCollector, Func merge, Func getNewCollector) + : base(source) + { + _getInitialCollector = getInitialCollector; + _merge = merge; + _getNewCollector = getNewCollector; + } + + protected override PushToPullSink Run() => new _(_merge, _getNewCollector, _getInitialCollector()); + + private sealed class _ : PushToPullSink + { + private readonly object _gate; + private readonly Func _merge; + private readonly Func _getNewCollector; + + public _(Func merge, Func getNewCollector, TResult collector) + { + _gate = new object(); + _merge = merge; + _getNewCollector = getNewCollector; + _collector = collector; + } + + private TResult _collector; + private Exception? _error; + private bool _hasCompleted; + private bool _done; + + public override void OnNext(TSource value) + { + lock (_gate) + { + try + { + _collector = _merge(_collector, value); + } + catch (Exception ex) + { + _error = ex; + + Dispose(); + } + } + } + + public override void OnError(Exception error) + { + Dispose(); + + lock (_gate) + { + _error = error; + } + } + + public override void OnCompleted() + { + Dispose(); + + lock (_gate) + { + _hasCompleted = true; + } + } + + public override bool TryMoveNext([MaybeNullWhen(false)] out TResult current) + { + lock (_gate) + { + var error = _error; + if (error != null) + { + current = default; + _collector = default!; + error.Throw(); + } + else + { + if (_hasCompleted) + { + if (_done) + { + current = default; + _collector = default!; + return false; + } + + current = _collector; + _done = true; + } + else + { + current = _collector; + + try + { + _collector = _getNewCollector(current); + } + catch + { + Dispose(); + throw; + } + } + } + + return true; + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/CombineLatest.NAry.cs b/LibExternal/System.Reactive/Linq/Observable/CombineLatest.NAry.cs new file mode 100644 index 0000000..7ae6940 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/CombineLatest.NAry.cs @@ -0,0 +1,1349 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + #region [3,16]-ary + + /* The following code is generated by a T4 template. */ + + #region CombineLatest auto-generated code (10/2/2020 12:15:44 PM) + + internal sealed class CombineLatest : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly Func _resultSelector; + + public CombineLatest(IObservable source1, IObservable source2, IObservable source3, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3); + + internal sealed class _ : CombineLatestSink + { + private readonly Func _resultSelector; + + private readonly CombineLatestObserver _observer1; + private readonly CombineLatestObserver _observer2; + private readonly CombineLatestObserver _observer3; + + public _(Func resultSelector, IObserver observer) + : base(3, observer) + { + _resultSelector = resultSelector; + + _observer1 = new CombineLatestObserver(_gate, this, 0); + _observer2 = new CombineLatestObserver(_gate, this, 1); + _observer3 = new CombineLatestObserver(_gate, this, 2); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3) + { + var subscriptions = new IDisposable[3]; + + subscriptions[0] = _observer1; + subscriptions[1] = _observer2; + subscriptions[2] = _observer3; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(subscriptions)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Value!, _observer2.Value!, _observer3.Value!); + } + } + + internal sealed class CombineLatest : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly Func _resultSelector; + + public CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4); + + internal sealed class _ : CombineLatestSink + { + private readonly Func _resultSelector; + + private readonly CombineLatestObserver _observer1; + private readonly CombineLatestObserver _observer2; + private readonly CombineLatestObserver _observer3; + private readonly CombineLatestObserver _observer4; + + public _(Func resultSelector, IObserver observer) + : base(4, observer) + { + _resultSelector = resultSelector; + + _observer1 = new CombineLatestObserver(_gate, this, 0); + _observer2 = new CombineLatestObserver(_gate, this, 1); + _observer3 = new CombineLatestObserver(_gate, this, 2); + _observer4 = new CombineLatestObserver(_gate, this, 3); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4) + { + var subscriptions = new IDisposable[4]; + + subscriptions[0] = _observer1; + subscriptions[1] = _observer2; + subscriptions[2] = _observer3; + subscriptions[3] = _observer4; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(subscriptions)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Value!, _observer2.Value!, _observer3.Value!, _observer4.Value!); + } + } + + internal sealed class CombineLatest : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly IObservable _source5; + private readonly Func _resultSelector; + + public CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _source5 = source5; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4, _source5); + + internal sealed class _ : CombineLatestSink + { + private readonly Func _resultSelector; + + private readonly CombineLatestObserver _observer1; + private readonly CombineLatestObserver _observer2; + private readonly CombineLatestObserver _observer3; + private readonly CombineLatestObserver _observer4; + private readonly CombineLatestObserver _observer5; + + public _(Func resultSelector, IObserver observer) + : base(5, observer) + { + _resultSelector = resultSelector; + + _observer1 = new CombineLatestObserver(_gate, this, 0); + _observer2 = new CombineLatestObserver(_gate, this, 1); + _observer3 = new CombineLatestObserver(_gate, this, 2); + _observer4 = new CombineLatestObserver(_gate, this, 3); + _observer5 = new CombineLatestObserver(_gate, this, 4); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5) + { + var subscriptions = new IDisposable[5]; + + subscriptions[0] = _observer1; + subscriptions[1] = _observer2; + subscriptions[2] = _observer3; + subscriptions[3] = _observer4; + subscriptions[4] = _observer5; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + _observer5.SetResource(source5.SubscribeSafe(_observer5)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(subscriptions)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Value!, _observer2.Value!, _observer3.Value!, _observer4.Value!, _observer5.Value!); + } + } + + internal sealed class CombineLatest : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly IObservable _source5; + private readonly IObservable _source6; + private readonly Func _resultSelector; + + public CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _source5 = source5; + _source6 = source6; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4, _source5, _source6); + + internal sealed class _ : CombineLatestSink + { + private readonly Func _resultSelector; + + private readonly CombineLatestObserver _observer1; + private readonly CombineLatestObserver _observer2; + private readonly CombineLatestObserver _observer3; + private readonly CombineLatestObserver _observer4; + private readonly CombineLatestObserver _observer5; + private readonly CombineLatestObserver _observer6; + + public _(Func resultSelector, IObserver observer) + : base(6, observer) + { + _resultSelector = resultSelector; + + _observer1 = new CombineLatestObserver(_gate, this, 0); + _observer2 = new CombineLatestObserver(_gate, this, 1); + _observer3 = new CombineLatestObserver(_gate, this, 2); + _observer4 = new CombineLatestObserver(_gate, this, 3); + _observer5 = new CombineLatestObserver(_gate, this, 4); + _observer6 = new CombineLatestObserver(_gate, this, 5); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6) + { + var subscriptions = new IDisposable[6]; + + subscriptions[0] = _observer1; + subscriptions[1] = _observer2; + subscriptions[2] = _observer3; + subscriptions[3] = _observer4; + subscriptions[4] = _observer5; + subscriptions[5] = _observer6; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + _observer5.SetResource(source5.SubscribeSafe(_observer5)); + _observer6.SetResource(source6.SubscribeSafe(_observer6)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(subscriptions)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Value!, _observer2.Value!, _observer3.Value!, _observer4.Value!, _observer5.Value!, _observer6.Value!); + } + } + + internal sealed class CombineLatest : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly IObservable _source5; + private readonly IObservable _source6; + private readonly IObservable _source7; + private readonly Func _resultSelector; + + public CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _source5 = source5; + _source6 = source6; + _source7 = source7; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4, _source5, _source6, _source7); + + internal sealed class _ : CombineLatestSink + { + private readonly Func _resultSelector; + + private readonly CombineLatestObserver _observer1; + private readonly CombineLatestObserver _observer2; + private readonly CombineLatestObserver _observer3; + private readonly CombineLatestObserver _observer4; + private readonly CombineLatestObserver _observer5; + private readonly CombineLatestObserver _observer6; + private readonly CombineLatestObserver _observer7; + + public _(Func resultSelector, IObserver observer) + : base(7, observer) + { + _resultSelector = resultSelector; + + _observer1 = new CombineLatestObserver(_gate, this, 0); + _observer2 = new CombineLatestObserver(_gate, this, 1); + _observer3 = new CombineLatestObserver(_gate, this, 2); + _observer4 = new CombineLatestObserver(_gate, this, 3); + _observer5 = new CombineLatestObserver(_gate, this, 4); + _observer6 = new CombineLatestObserver(_gate, this, 5); + _observer7 = new CombineLatestObserver(_gate, this, 6); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7) + { + var subscriptions = new IDisposable[7]; + + subscriptions[0] = _observer1; + subscriptions[1] = _observer2; + subscriptions[2] = _observer3; + subscriptions[3] = _observer4; + subscriptions[4] = _observer5; + subscriptions[5] = _observer6; + subscriptions[6] = _observer7; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + _observer5.SetResource(source5.SubscribeSafe(_observer5)); + _observer6.SetResource(source6.SubscribeSafe(_observer6)); + _observer7.SetResource(source7.SubscribeSafe(_observer7)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(subscriptions)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Value!, _observer2.Value!, _observer3.Value!, _observer4.Value!, _observer5.Value!, _observer6.Value!, _observer7.Value!); + } + } + + internal sealed class CombineLatest : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly IObservable _source5; + private readonly IObservable _source6; + private readonly IObservable _source7; + private readonly IObservable _source8; + private readonly Func _resultSelector; + + public CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _source5 = source5; + _source6 = source6; + _source7 = source7; + _source8 = source8; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4, _source5, _source6, _source7, _source8); + + internal sealed class _ : CombineLatestSink + { + private readonly Func _resultSelector; + + private readonly CombineLatestObserver _observer1; + private readonly CombineLatestObserver _observer2; + private readonly CombineLatestObserver _observer3; + private readonly CombineLatestObserver _observer4; + private readonly CombineLatestObserver _observer5; + private readonly CombineLatestObserver _observer6; + private readonly CombineLatestObserver _observer7; + private readonly CombineLatestObserver _observer8; + + public _(Func resultSelector, IObserver observer) + : base(8, observer) + { + _resultSelector = resultSelector; + + _observer1 = new CombineLatestObserver(_gate, this, 0); + _observer2 = new CombineLatestObserver(_gate, this, 1); + _observer3 = new CombineLatestObserver(_gate, this, 2); + _observer4 = new CombineLatestObserver(_gate, this, 3); + _observer5 = new CombineLatestObserver(_gate, this, 4); + _observer6 = new CombineLatestObserver(_gate, this, 5); + _observer7 = new CombineLatestObserver(_gate, this, 6); + _observer8 = new CombineLatestObserver(_gate, this, 7); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8) + { + var subscriptions = new IDisposable[8]; + + subscriptions[0] = _observer1; + subscriptions[1] = _observer2; + subscriptions[2] = _observer3; + subscriptions[3] = _observer4; + subscriptions[4] = _observer5; + subscriptions[5] = _observer6; + subscriptions[6] = _observer7; + subscriptions[7] = _observer8; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + _observer5.SetResource(source5.SubscribeSafe(_observer5)); + _observer6.SetResource(source6.SubscribeSafe(_observer6)); + _observer7.SetResource(source7.SubscribeSafe(_observer7)); + _observer8.SetResource(source8.SubscribeSafe(_observer8)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(subscriptions)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Value!, _observer2.Value!, _observer3.Value!, _observer4.Value!, _observer5.Value!, _observer6.Value!, _observer7.Value!, _observer8.Value!); + } + } + + internal sealed class CombineLatest : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly IObservable _source5; + private readonly IObservable _source6; + private readonly IObservable _source7; + private readonly IObservable _source8; + private readonly IObservable _source9; + private readonly Func _resultSelector; + + public CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _source5 = source5; + _source6 = source6; + _source7 = source7; + _source8 = source8; + _source9 = source9; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4, _source5, _source6, _source7, _source8, _source9); + + internal sealed class _ : CombineLatestSink + { + private readonly Func _resultSelector; + + private readonly CombineLatestObserver _observer1; + private readonly CombineLatestObserver _observer2; + private readonly CombineLatestObserver _observer3; + private readonly CombineLatestObserver _observer4; + private readonly CombineLatestObserver _observer5; + private readonly CombineLatestObserver _observer6; + private readonly CombineLatestObserver _observer7; + private readonly CombineLatestObserver _observer8; + private readonly CombineLatestObserver _observer9; + + public _(Func resultSelector, IObserver observer) + : base(9, observer) + { + _resultSelector = resultSelector; + + _observer1 = new CombineLatestObserver(_gate, this, 0); + _observer2 = new CombineLatestObserver(_gate, this, 1); + _observer3 = new CombineLatestObserver(_gate, this, 2); + _observer4 = new CombineLatestObserver(_gate, this, 3); + _observer5 = new CombineLatestObserver(_gate, this, 4); + _observer6 = new CombineLatestObserver(_gate, this, 5); + _observer7 = new CombineLatestObserver(_gate, this, 6); + _observer8 = new CombineLatestObserver(_gate, this, 7); + _observer9 = new CombineLatestObserver(_gate, this, 8); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9) + { + var subscriptions = new IDisposable[9]; + + subscriptions[0] = _observer1; + subscriptions[1] = _observer2; + subscriptions[2] = _observer3; + subscriptions[3] = _observer4; + subscriptions[4] = _observer5; + subscriptions[5] = _observer6; + subscriptions[6] = _observer7; + subscriptions[7] = _observer8; + subscriptions[8] = _observer9; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + _observer5.SetResource(source5.SubscribeSafe(_observer5)); + _observer6.SetResource(source6.SubscribeSafe(_observer6)); + _observer7.SetResource(source7.SubscribeSafe(_observer7)); + _observer8.SetResource(source8.SubscribeSafe(_observer8)); + _observer9.SetResource(source9.SubscribeSafe(_observer9)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(subscriptions)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Value!, _observer2.Value!, _observer3.Value!, _observer4.Value!, _observer5.Value!, _observer6.Value!, _observer7.Value!, _observer8.Value!, _observer9.Value!); + } + } + + internal sealed class CombineLatest : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly IObservable _source5; + private readonly IObservable _source6; + private readonly IObservable _source7; + private readonly IObservable _source8; + private readonly IObservable _source9; + private readonly IObservable _source10; + private readonly Func _resultSelector; + + public CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _source5 = source5; + _source6 = source6; + _source7 = source7; + _source8 = source8; + _source9 = source9; + _source10 = source10; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4, _source5, _source6, _source7, _source8, _source9, _source10); + + internal sealed class _ : CombineLatestSink + { + private readonly Func _resultSelector; + + private readonly CombineLatestObserver _observer1; + private readonly CombineLatestObserver _observer2; + private readonly CombineLatestObserver _observer3; + private readonly CombineLatestObserver _observer4; + private readonly CombineLatestObserver _observer5; + private readonly CombineLatestObserver _observer6; + private readonly CombineLatestObserver _observer7; + private readonly CombineLatestObserver _observer8; + private readonly CombineLatestObserver _observer9; + private readonly CombineLatestObserver _observer10; + + public _(Func resultSelector, IObserver observer) + : base(10, observer) + { + _resultSelector = resultSelector; + + _observer1 = new CombineLatestObserver(_gate, this, 0); + _observer2 = new CombineLatestObserver(_gate, this, 1); + _observer3 = new CombineLatestObserver(_gate, this, 2); + _observer4 = new CombineLatestObserver(_gate, this, 3); + _observer5 = new CombineLatestObserver(_gate, this, 4); + _observer6 = new CombineLatestObserver(_gate, this, 5); + _observer7 = new CombineLatestObserver(_gate, this, 6); + _observer8 = new CombineLatestObserver(_gate, this, 7); + _observer9 = new CombineLatestObserver(_gate, this, 8); + _observer10 = new CombineLatestObserver(_gate, this, 9); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10) + { + var subscriptions = new IDisposable[10]; + + subscriptions[0] = _observer1; + subscriptions[1] = _observer2; + subscriptions[2] = _observer3; + subscriptions[3] = _observer4; + subscriptions[4] = _observer5; + subscriptions[5] = _observer6; + subscriptions[6] = _observer7; + subscriptions[7] = _observer8; + subscriptions[8] = _observer9; + subscriptions[9] = _observer10; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + _observer5.SetResource(source5.SubscribeSafe(_observer5)); + _observer6.SetResource(source6.SubscribeSafe(_observer6)); + _observer7.SetResource(source7.SubscribeSafe(_observer7)); + _observer8.SetResource(source8.SubscribeSafe(_observer8)); + _observer9.SetResource(source9.SubscribeSafe(_observer9)); + _observer10.SetResource(source10.SubscribeSafe(_observer10)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(subscriptions)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Value!, _observer2.Value!, _observer3.Value!, _observer4.Value!, _observer5.Value!, _observer6.Value!, _observer7.Value!, _observer8.Value!, _observer9.Value!, _observer10.Value!); + } + } + + internal sealed class CombineLatest : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly IObservable _source5; + private readonly IObservable _source6; + private readonly IObservable _source7; + private readonly IObservable _source8; + private readonly IObservable _source9; + private readonly IObservable _source10; + private readonly IObservable _source11; + private readonly Func _resultSelector; + + public CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _source5 = source5; + _source6 = source6; + _source7 = source7; + _source8 = source8; + _source9 = source9; + _source10 = source10; + _source11 = source11; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4, _source5, _source6, _source7, _source8, _source9, _source10, _source11); + + internal sealed class _ : CombineLatestSink + { + private readonly Func _resultSelector; + + private readonly CombineLatestObserver _observer1; + private readonly CombineLatestObserver _observer2; + private readonly CombineLatestObserver _observer3; + private readonly CombineLatestObserver _observer4; + private readonly CombineLatestObserver _observer5; + private readonly CombineLatestObserver _observer6; + private readonly CombineLatestObserver _observer7; + private readonly CombineLatestObserver _observer8; + private readonly CombineLatestObserver _observer9; + private readonly CombineLatestObserver _observer10; + private readonly CombineLatestObserver _observer11; + + public _(Func resultSelector, IObserver observer) + : base(11, observer) + { + _resultSelector = resultSelector; + + _observer1 = new CombineLatestObserver(_gate, this, 0); + _observer2 = new CombineLatestObserver(_gate, this, 1); + _observer3 = new CombineLatestObserver(_gate, this, 2); + _observer4 = new CombineLatestObserver(_gate, this, 3); + _observer5 = new CombineLatestObserver(_gate, this, 4); + _observer6 = new CombineLatestObserver(_gate, this, 5); + _observer7 = new CombineLatestObserver(_gate, this, 6); + _observer8 = new CombineLatestObserver(_gate, this, 7); + _observer9 = new CombineLatestObserver(_gate, this, 8); + _observer10 = new CombineLatestObserver(_gate, this, 9); + _observer11 = new CombineLatestObserver(_gate, this, 10); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11) + { + var subscriptions = new IDisposable[11]; + + subscriptions[0] = _observer1; + subscriptions[1] = _observer2; + subscriptions[2] = _observer3; + subscriptions[3] = _observer4; + subscriptions[4] = _observer5; + subscriptions[5] = _observer6; + subscriptions[6] = _observer7; + subscriptions[7] = _observer8; + subscriptions[8] = _observer9; + subscriptions[9] = _observer10; + subscriptions[10] = _observer11; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + _observer5.SetResource(source5.SubscribeSafe(_observer5)); + _observer6.SetResource(source6.SubscribeSafe(_observer6)); + _observer7.SetResource(source7.SubscribeSafe(_observer7)); + _observer8.SetResource(source8.SubscribeSafe(_observer8)); + _observer9.SetResource(source9.SubscribeSafe(_observer9)); + _observer10.SetResource(source10.SubscribeSafe(_observer10)); + _observer11.SetResource(source11.SubscribeSafe(_observer11)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(subscriptions)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Value!, _observer2.Value!, _observer3.Value!, _observer4.Value!, _observer5.Value!, _observer6.Value!, _observer7.Value!, _observer8.Value!, _observer9.Value!, _observer10.Value!, _observer11.Value!); + } + } + + internal sealed class CombineLatest : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly IObservable _source5; + private readonly IObservable _source6; + private readonly IObservable _source7; + private readonly IObservable _source8; + private readonly IObservable _source9; + private readonly IObservable _source10; + private readonly IObservable _source11; + private readonly IObservable _source12; + private readonly Func _resultSelector; + + public CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _source5 = source5; + _source6 = source6; + _source7 = source7; + _source8 = source8; + _source9 = source9; + _source10 = source10; + _source11 = source11; + _source12 = source12; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4, _source5, _source6, _source7, _source8, _source9, _source10, _source11, _source12); + + internal sealed class _ : CombineLatestSink + { + private readonly Func _resultSelector; + + private readonly CombineLatestObserver _observer1; + private readonly CombineLatestObserver _observer2; + private readonly CombineLatestObserver _observer3; + private readonly CombineLatestObserver _observer4; + private readonly CombineLatestObserver _observer5; + private readonly CombineLatestObserver _observer6; + private readonly CombineLatestObserver _observer7; + private readonly CombineLatestObserver _observer8; + private readonly CombineLatestObserver _observer9; + private readonly CombineLatestObserver _observer10; + private readonly CombineLatestObserver _observer11; + private readonly CombineLatestObserver _observer12; + + public _(Func resultSelector, IObserver observer) + : base(12, observer) + { + _resultSelector = resultSelector; + + _observer1 = new CombineLatestObserver(_gate, this, 0); + _observer2 = new CombineLatestObserver(_gate, this, 1); + _observer3 = new CombineLatestObserver(_gate, this, 2); + _observer4 = new CombineLatestObserver(_gate, this, 3); + _observer5 = new CombineLatestObserver(_gate, this, 4); + _observer6 = new CombineLatestObserver(_gate, this, 5); + _observer7 = new CombineLatestObserver(_gate, this, 6); + _observer8 = new CombineLatestObserver(_gate, this, 7); + _observer9 = new CombineLatestObserver(_gate, this, 8); + _observer10 = new CombineLatestObserver(_gate, this, 9); + _observer11 = new CombineLatestObserver(_gate, this, 10); + _observer12 = new CombineLatestObserver(_gate, this, 11); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12) + { + var subscriptions = new IDisposable[12]; + + subscriptions[0] = _observer1; + subscriptions[1] = _observer2; + subscriptions[2] = _observer3; + subscriptions[3] = _observer4; + subscriptions[4] = _observer5; + subscriptions[5] = _observer6; + subscriptions[6] = _observer7; + subscriptions[7] = _observer8; + subscriptions[8] = _observer9; + subscriptions[9] = _observer10; + subscriptions[10] = _observer11; + subscriptions[11] = _observer12; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + _observer5.SetResource(source5.SubscribeSafe(_observer5)); + _observer6.SetResource(source6.SubscribeSafe(_observer6)); + _observer7.SetResource(source7.SubscribeSafe(_observer7)); + _observer8.SetResource(source8.SubscribeSafe(_observer8)); + _observer9.SetResource(source9.SubscribeSafe(_observer9)); + _observer10.SetResource(source10.SubscribeSafe(_observer10)); + _observer11.SetResource(source11.SubscribeSafe(_observer11)); + _observer12.SetResource(source12.SubscribeSafe(_observer12)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(subscriptions)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Value!, _observer2.Value!, _observer3.Value!, _observer4.Value!, _observer5.Value!, _observer6.Value!, _observer7.Value!, _observer8.Value!, _observer9.Value!, _observer10.Value!, _observer11.Value!, _observer12.Value!); + } + } + + internal sealed class CombineLatest : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly IObservable _source5; + private readonly IObservable _source6; + private readonly IObservable _source7; + private readonly IObservable _source8; + private readonly IObservable _source9; + private readonly IObservable _source10; + private readonly IObservable _source11; + private readonly IObservable _source12; + private readonly IObservable _source13; + private readonly Func _resultSelector; + + public CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _source5 = source5; + _source6 = source6; + _source7 = source7; + _source8 = source8; + _source9 = source9; + _source10 = source10; + _source11 = source11; + _source12 = source12; + _source13 = source13; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4, _source5, _source6, _source7, _source8, _source9, _source10, _source11, _source12, _source13); + + internal sealed class _ : CombineLatestSink + { + private readonly Func _resultSelector; + + private readonly CombineLatestObserver _observer1; + private readonly CombineLatestObserver _observer2; + private readonly CombineLatestObserver _observer3; + private readonly CombineLatestObserver _observer4; + private readonly CombineLatestObserver _observer5; + private readonly CombineLatestObserver _observer6; + private readonly CombineLatestObserver _observer7; + private readonly CombineLatestObserver _observer8; + private readonly CombineLatestObserver _observer9; + private readonly CombineLatestObserver _observer10; + private readonly CombineLatestObserver _observer11; + private readonly CombineLatestObserver _observer12; + private readonly CombineLatestObserver _observer13; + + public _(Func resultSelector, IObserver observer) + : base(13, observer) + { + _resultSelector = resultSelector; + + _observer1 = new CombineLatestObserver(_gate, this, 0); + _observer2 = new CombineLatestObserver(_gate, this, 1); + _observer3 = new CombineLatestObserver(_gate, this, 2); + _observer4 = new CombineLatestObserver(_gate, this, 3); + _observer5 = new CombineLatestObserver(_gate, this, 4); + _observer6 = new CombineLatestObserver(_gate, this, 5); + _observer7 = new CombineLatestObserver(_gate, this, 6); + _observer8 = new CombineLatestObserver(_gate, this, 7); + _observer9 = new CombineLatestObserver(_gate, this, 8); + _observer10 = new CombineLatestObserver(_gate, this, 9); + _observer11 = new CombineLatestObserver(_gate, this, 10); + _observer12 = new CombineLatestObserver(_gate, this, 11); + _observer13 = new CombineLatestObserver(_gate, this, 12); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13) + { + var subscriptions = new IDisposable[13]; + + subscriptions[0] = _observer1; + subscriptions[1] = _observer2; + subscriptions[2] = _observer3; + subscriptions[3] = _observer4; + subscriptions[4] = _observer5; + subscriptions[5] = _observer6; + subscriptions[6] = _observer7; + subscriptions[7] = _observer8; + subscriptions[8] = _observer9; + subscriptions[9] = _observer10; + subscriptions[10] = _observer11; + subscriptions[11] = _observer12; + subscriptions[12] = _observer13; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + _observer5.SetResource(source5.SubscribeSafe(_observer5)); + _observer6.SetResource(source6.SubscribeSafe(_observer6)); + _observer7.SetResource(source7.SubscribeSafe(_observer7)); + _observer8.SetResource(source8.SubscribeSafe(_observer8)); + _observer9.SetResource(source9.SubscribeSafe(_observer9)); + _observer10.SetResource(source10.SubscribeSafe(_observer10)); + _observer11.SetResource(source11.SubscribeSafe(_observer11)); + _observer12.SetResource(source12.SubscribeSafe(_observer12)); + _observer13.SetResource(source13.SubscribeSafe(_observer13)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(subscriptions)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Value!, _observer2.Value!, _observer3.Value!, _observer4.Value!, _observer5.Value!, _observer6.Value!, _observer7.Value!, _observer8.Value!, _observer9.Value!, _observer10.Value!, _observer11.Value!, _observer12.Value!, _observer13.Value!); + } + } + + internal sealed class CombineLatest : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly IObservable _source5; + private readonly IObservable _source6; + private readonly IObservable _source7; + private readonly IObservable _source8; + private readonly IObservable _source9; + private readonly IObservable _source10; + private readonly IObservable _source11; + private readonly IObservable _source12; + private readonly IObservable _source13; + private readonly IObservable _source14; + private readonly Func _resultSelector; + + public CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _source5 = source5; + _source6 = source6; + _source7 = source7; + _source8 = source8; + _source9 = source9; + _source10 = source10; + _source11 = source11; + _source12 = source12; + _source13 = source13; + _source14 = source14; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4, _source5, _source6, _source7, _source8, _source9, _source10, _source11, _source12, _source13, _source14); + + internal sealed class _ : CombineLatestSink + { + private readonly Func _resultSelector; + + private readonly CombineLatestObserver _observer1; + private readonly CombineLatestObserver _observer2; + private readonly CombineLatestObserver _observer3; + private readonly CombineLatestObserver _observer4; + private readonly CombineLatestObserver _observer5; + private readonly CombineLatestObserver _observer6; + private readonly CombineLatestObserver _observer7; + private readonly CombineLatestObserver _observer8; + private readonly CombineLatestObserver _observer9; + private readonly CombineLatestObserver _observer10; + private readonly CombineLatestObserver _observer11; + private readonly CombineLatestObserver _observer12; + private readonly CombineLatestObserver _observer13; + private readonly CombineLatestObserver _observer14; + + public _(Func resultSelector, IObserver observer) + : base(14, observer) + { + _resultSelector = resultSelector; + + _observer1 = new CombineLatestObserver(_gate, this, 0); + _observer2 = new CombineLatestObserver(_gate, this, 1); + _observer3 = new CombineLatestObserver(_gate, this, 2); + _observer4 = new CombineLatestObserver(_gate, this, 3); + _observer5 = new CombineLatestObserver(_gate, this, 4); + _observer6 = new CombineLatestObserver(_gate, this, 5); + _observer7 = new CombineLatestObserver(_gate, this, 6); + _observer8 = new CombineLatestObserver(_gate, this, 7); + _observer9 = new CombineLatestObserver(_gate, this, 8); + _observer10 = new CombineLatestObserver(_gate, this, 9); + _observer11 = new CombineLatestObserver(_gate, this, 10); + _observer12 = new CombineLatestObserver(_gate, this, 11); + _observer13 = new CombineLatestObserver(_gate, this, 12); + _observer14 = new CombineLatestObserver(_gate, this, 13); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14) + { + var subscriptions = new IDisposable[14]; + + subscriptions[0] = _observer1; + subscriptions[1] = _observer2; + subscriptions[2] = _observer3; + subscriptions[3] = _observer4; + subscriptions[4] = _observer5; + subscriptions[5] = _observer6; + subscriptions[6] = _observer7; + subscriptions[7] = _observer8; + subscriptions[8] = _observer9; + subscriptions[9] = _observer10; + subscriptions[10] = _observer11; + subscriptions[11] = _observer12; + subscriptions[12] = _observer13; + subscriptions[13] = _observer14; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + _observer5.SetResource(source5.SubscribeSafe(_observer5)); + _observer6.SetResource(source6.SubscribeSafe(_observer6)); + _observer7.SetResource(source7.SubscribeSafe(_observer7)); + _observer8.SetResource(source8.SubscribeSafe(_observer8)); + _observer9.SetResource(source9.SubscribeSafe(_observer9)); + _observer10.SetResource(source10.SubscribeSafe(_observer10)); + _observer11.SetResource(source11.SubscribeSafe(_observer11)); + _observer12.SetResource(source12.SubscribeSafe(_observer12)); + _observer13.SetResource(source13.SubscribeSafe(_observer13)); + _observer14.SetResource(source14.SubscribeSafe(_observer14)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(subscriptions)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Value!, _observer2.Value!, _observer3.Value!, _observer4.Value!, _observer5.Value!, _observer6.Value!, _observer7.Value!, _observer8.Value!, _observer9.Value!, _observer10.Value!, _observer11.Value!, _observer12.Value!, _observer13.Value!, _observer14.Value!); + } + } + + internal sealed class CombineLatest : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly IObservable _source5; + private readonly IObservable _source6; + private readonly IObservable _source7; + private readonly IObservable _source8; + private readonly IObservable _source9; + private readonly IObservable _source10; + private readonly IObservable _source11; + private readonly IObservable _source12; + private readonly IObservable _source13; + private readonly IObservable _source14; + private readonly IObservable _source15; + private readonly Func _resultSelector; + + public CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, IObservable source15, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _source5 = source5; + _source6 = source6; + _source7 = source7; + _source8 = source8; + _source9 = source9; + _source10 = source10; + _source11 = source11; + _source12 = source12; + _source13 = source13; + _source14 = source14; + _source15 = source15; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4, _source5, _source6, _source7, _source8, _source9, _source10, _source11, _source12, _source13, _source14, _source15); + + internal sealed class _ : CombineLatestSink + { + private readonly Func _resultSelector; + + private readonly CombineLatestObserver _observer1; + private readonly CombineLatestObserver _observer2; + private readonly CombineLatestObserver _observer3; + private readonly CombineLatestObserver _observer4; + private readonly CombineLatestObserver _observer5; + private readonly CombineLatestObserver _observer6; + private readonly CombineLatestObserver _observer7; + private readonly CombineLatestObserver _observer8; + private readonly CombineLatestObserver _observer9; + private readonly CombineLatestObserver _observer10; + private readonly CombineLatestObserver _observer11; + private readonly CombineLatestObserver _observer12; + private readonly CombineLatestObserver _observer13; + private readonly CombineLatestObserver _observer14; + private readonly CombineLatestObserver _observer15; + + public _(Func resultSelector, IObserver observer) + : base(15, observer) + { + _resultSelector = resultSelector; + + _observer1 = new CombineLatestObserver(_gate, this, 0); + _observer2 = new CombineLatestObserver(_gate, this, 1); + _observer3 = new CombineLatestObserver(_gate, this, 2); + _observer4 = new CombineLatestObserver(_gate, this, 3); + _observer5 = new CombineLatestObserver(_gate, this, 4); + _observer6 = new CombineLatestObserver(_gate, this, 5); + _observer7 = new CombineLatestObserver(_gate, this, 6); + _observer8 = new CombineLatestObserver(_gate, this, 7); + _observer9 = new CombineLatestObserver(_gate, this, 8); + _observer10 = new CombineLatestObserver(_gate, this, 9); + _observer11 = new CombineLatestObserver(_gate, this, 10); + _observer12 = new CombineLatestObserver(_gate, this, 11); + _observer13 = new CombineLatestObserver(_gate, this, 12); + _observer14 = new CombineLatestObserver(_gate, this, 13); + _observer15 = new CombineLatestObserver(_gate, this, 14); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, IObservable source15) + { + var subscriptions = new IDisposable[15]; + + subscriptions[0] = _observer1; + subscriptions[1] = _observer2; + subscriptions[2] = _observer3; + subscriptions[3] = _observer4; + subscriptions[4] = _observer5; + subscriptions[5] = _observer6; + subscriptions[6] = _observer7; + subscriptions[7] = _observer8; + subscriptions[8] = _observer9; + subscriptions[9] = _observer10; + subscriptions[10] = _observer11; + subscriptions[11] = _observer12; + subscriptions[12] = _observer13; + subscriptions[13] = _observer14; + subscriptions[14] = _observer15; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + _observer5.SetResource(source5.SubscribeSafe(_observer5)); + _observer6.SetResource(source6.SubscribeSafe(_observer6)); + _observer7.SetResource(source7.SubscribeSafe(_observer7)); + _observer8.SetResource(source8.SubscribeSafe(_observer8)); + _observer9.SetResource(source9.SubscribeSafe(_observer9)); + _observer10.SetResource(source10.SubscribeSafe(_observer10)); + _observer11.SetResource(source11.SubscribeSafe(_observer11)); + _observer12.SetResource(source12.SubscribeSafe(_observer12)); + _observer13.SetResource(source13.SubscribeSafe(_observer13)); + _observer14.SetResource(source14.SubscribeSafe(_observer14)); + _observer15.SetResource(source15.SubscribeSafe(_observer15)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(subscriptions)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Value!, _observer2.Value!, _observer3.Value!, _observer4.Value!, _observer5.Value!, _observer6.Value!, _observer7.Value!, _observer8.Value!, _observer9.Value!, _observer10.Value!, _observer11.Value!, _observer12.Value!, _observer13.Value!, _observer14.Value!, _observer15.Value!); + } + } + + internal sealed class CombineLatest : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly IObservable _source5; + private readonly IObservable _source6; + private readonly IObservable _source7; + private readonly IObservable _source8; + private readonly IObservable _source9; + private readonly IObservable _source10; + private readonly IObservable _source11; + private readonly IObservable _source12; + private readonly IObservable _source13; + private readonly IObservable _source14; + private readonly IObservable _source15; + private readonly IObservable _source16; + private readonly Func _resultSelector; + + public CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, IObservable source15, IObservable source16, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _source5 = source5; + _source6 = source6; + _source7 = source7; + _source8 = source8; + _source9 = source9; + _source10 = source10; + _source11 = source11; + _source12 = source12; + _source13 = source13; + _source14 = source14; + _source15 = source15; + _source16 = source16; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4, _source5, _source6, _source7, _source8, _source9, _source10, _source11, _source12, _source13, _source14, _source15, _source16); + + internal sealed class _ : CombineLatestSink + { + private readonly Func _resultSelector; + + private readonly CombineLatestObserver _observer1; + private readonly CombineLatestObserver _observer2; + private readonly CombineLatestObserver _observer3; + private readonly CombineLatestObserver _observer4; + private readonly CombineLatestObserver _observer5; + private readonly CombineLatestObserver _observer6; + private readonly CombineLatestObserver _observer7; + private readonly CombineLatestObserver _observer8; + private readonly CombineLatestObserver _observer9; + private readonly CombineLatestObserver _observer10; + private readonly CombineLatestObserver _observer11; + private readonly CombineLatestObserver _observer12; + private readonly CombineLatestObserver _observer13; + private readonly CombineLatestObserver _observer14; + private readonly CombineLatestObserver _observer15; + private readonly CombineLatestObserver _observer16; + + public _(Func resultSelector, IObserver observer) + : base(16, observer) + { + _resultSelector = resultSelector; + + _observer1 = new CombineLatestObserver(_gate, this, 0); + _observer2 = new CombineLatestObserver(_gate, this, 1); + _observer3 = new CombineLatestObserver(_gate, this, 2); + _observer4 = new CombineLatestObserver(_gate, this, 3); + _observer5 = new CombineLatestObserver(_gate, this, 4); + _observer6 = new CombineLatestObserver(_gate, this, 5); + _observer7 = new CombineLatestObserver(_gate, this, 6); + _observer8 = new CombineLatestObserver(_gate, this, 7); + _observer9 = new CombineLatestObserver(_gate, this, 8); + _observer10 = new CombineLatestObserver(_gate, this, 9); + _observer11 = new CombineLatestObserver(_gate, this, 10); + _observer12 = new CombineLatestObserver(_gate, this, 11); + _observer13 = new CombineLatestObserver(_gate, this, 12); + _observer14 = new CombineLatestObserver(_gate, this, 13); + _observer15 = new CombineLatestObserver(_gate, this, 14); + _observer16 = new CombineLatestObserver(_gate, this, 15); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, IObservable source15, IObservable source16) + { + var subscriptions = new IDisposable[16]; + + subscriptions[0] = _observer1; + subscriptions[1] = _observer2; + subscriptions[2] = _observer3; + subscriptions[3] = _observer4; + subscriptions[4] = _observer5; + subscriptions[5] = _observer6; + subscriptions[6] = _observer7; + subscriptions[7] = _observer8; + subscriptions[8] = _observer9; + subscriptions[9] = _observer10; + subscriptions[10] = _observer11; + subscriptions[11] = _observer12; + subscriptions[12] = _observer13; + subscriptions[13] = _observer14; + subscriptions[14] = _observer15; + subscriptions[15] = _observer16; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + _observer5.SetResource(source5.SubscribeSafe(_observer5)); + _observer6.SetResource(source6.SubscribeSafe(_observer6)); + _observer7.SetResource(source7.SubscribeSafe(_observer7)); + _observer8.SetResource(source8.SubscribeSafe(_observer8)); + _observer9.SetResource(source9.SubscribeSafe(_observer9)); + _observer10.SetResource(source10.SubscribeSafe(_observer10)); + _observer11.SetResource(source11.SubscribeSafe(_observer11)); + _observer12.SetResource(source12.SubscribeSafe(_observer12)); + _observer13.SetResource(source13.SubscribeSafe(_observer13)); + _observer14.SetResource(source14.SubscribeSafe(_observer14)); + _observer15.SetResource(source15.SubscribeSafe(_observer15)); + _observer16.SetResource(source16.SubscribeSafe(_observer16)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(subscriptions)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Value!, _observer2.Value!, _observer3.Value!, _observer4.Value!, _observer5.Value!, _observer6.Value!, _observer7.Value!, _observer8.Value!, _observer9.Value!, _observer10.Value!, _observer11.Value!, _observer12.Value!, _observer13.Value!, _observer14.Value!, _observer15.Value!, _observer16.Value!); + } + } + + + #endregion + + #endregion +} diff --git a/LibExternal/System.Reactive/Linq/Observable/CombineLatest.NAry.tt b/LibExternal/System.Reactive/Linq/Observable/CombineLatest.NAry.tt new file mode 100644 index 0000000..915432f --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/CombineLatest.NAry.tt @@ -0,0 +1,122 @@ +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + #region [3,16]-ary + + /* The following code is generated by a T4 template. */ + + #region CombineLatest auto-generated code (<#=DateTime.Now.ToString()#>) + +<# +for (var i = 3; i <= 16; i++) +{ + var ts = string.Join(", ", Enumerable.Range(1, i).Select(j => "T" + j)); + var os = string.Join(", ", Enumerable.Range(1, i).Select(j => "IObservable source" + j)); + var vs = string.Join(", ", Enumerable.Range(1, i).Select(j => "_observer" + j + ".Value!")); + var ss = string.Join(", ", Enumerable.Range(1, i).Select(j => "_source" + j)); +#> + internal sealed class CombineLatest<<#=ts#>, TResult> : Producer, TResult>._> + { +<# +for (var j = 1; j <= i; j++) +{ +#> + private readonly IObservable> _source<#=j#>; +<# +} +#> + private readonly Func<<#=ts#>, TResult> _resultSelector; + + public CombineLatest(<#=os#>, Func<<#=ts#>, TResult> resultSelector) + { +<# +for (var j = 1; j <= i; j++) +{ +#> + _source<#=j#> = source<#=j#>; +<# +} +#> + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new _(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(<#=ss#>); + + internal sealed class _ : CombineLatestSink + { + private readonly Func<<#=ts#>, TResult> _resultSelector; + +<# +for (var j = 1; j <= i; j++) +{ +#> + private readonly CombineLatestObserver> _observer<#=j#>; +<# +} +#> + + public _(Func<<#=ts#>, TResult> resultSelector, IObserver observer) + : base(<#=i#>, observer) + { + _resultSelector = resultSelector; + +<# +for (var j = 1; j <= i; j++) +{ +#> + _observer<#=j#> = new CombineLatestObserver>(_gate, this, <#=j - 1#>); +<# +} +#> + } + + public void Run(<#=os#>) + { + var subscriptions = new IDisposable[<#=i#>]; + +<# +for (var j = 1; j <= i; j++) +{ +#> + subscriptions[<#=j - 1#>] = _observer<#=j#>; +<# +} +#> + +<# +for (var j = 1; j <= i; j++) +{ +#> + _observer<#=j#>.SetResource(source<#=j#>.SubscribeSafe(_observer<#=j#>)); +<# +} +#> + + SetUpstream(StableCompositeDisposable.CreateTrusted(subscriptions)); + } + + protected override TResult GetResult() => _resultSelector(<#=vs#>); + } + } + +<# +} +#> + + #endregion + + #endregion +} diff --git a/LibExternal/System.Reactive/Linq/Observable/CombineLatest.cs b/LibExternal/System.Reactive/Linq/Observable/CombineLatest.cs new file mode 100644 index 0000000..bfca05a --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/CombineLatest.cs @@ -0,0 +1,528 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + #region Binary + + internal sealed class CombineLatest : Producer._> + { + private readonly IObservable _first; + private readonly IObservable _second; + private readonly Func _resultSelector; + + public CombineLatest(IObservable first, IObservable second, Func resultSelector) + { + _first = first; + _second = second; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_first, _second); + + internal sealed class _ : IdentitySink + { + private readonly Func _resultSelector; + private readonly object _gate = new(); + + public _(Func resultSelector, IObserver observer) + : base(observer) + { + _resultSelector = resultSelector; + } + + private SingleAssignmentDisposableValue _firstDisposable; + private SingleAssignmentDisposableValue _secondDisposable; + + public void Run(IObservable first, IObservable second) + { + var fstO = new FirstObserver(this); + var sndO = new SecondObserver(this); + + fstO.SetOther(sndO); + sndO.SetOther(fstO); + + _firstDisposable.Disposable = first.SubscribeSafe(fstO); + _secondDisposable.Disposable = second.SubscribeSafe(sndO); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _firstDisposable.Dispose(); + _secondDisposable.Dispose(); + } + + base.Dispose(disposing); + } + + private sealed class FirstObserver : IObserver + { + private readonly _ _parent; + private SecondObserver _other; + + public FirstObserver(_ parent) + { + _parent = parent; + _other = default!; // NB: Will be set by SetOther. + } + + public void SetOther(SecondObserver other) { _other = other; } + + public bool HasValue { get; private set; } + public TFirst? Value { get; private set; } + public bool Done { get; private set; } + + public void OnNext(TFirst value) + { + lock (_parent._gate) + { + HasValue = true; + Value = value; + + if (_other.HasValue) + { + TResult res; + try + { + res = _parent._resultSelector(value, _other.Value!); + } + catch (Exception ex) + { + _parent.ForwardOnError(ex); + return; + } + + _parent.ForwardOnNext(res); + } + else if (_other.Done) + { + _parent.ForwardOnCompleted(); + } + } + } + + public void OnError(Exception error) + { + lock (_parent._gate) + { + _parent.ForwardOnError(error); + } + } + + public void OnCompleted() + { + lock (_parent._gate) + { + Done = true; + + if (_other.Done) + { + _parent.ForwardOnCompleted(); + } + else + { + _parent._firstDisposable.Dispose(); + } + } + } + } + + private sealed class SecondObserver : IObserver + { + private readonly _ _parent; + private FirstObserver _other; + + public SecondObserver(_ parent) + { + _parent = parent; + _other = default!; // NB: Will be set by SetOther. + } + + public void SetOther(FirstObserver other) { _other = other; } + + public bool HasValue { get; private set; } + public TSecond? Value { get; private set; } + public bool Done { get; private set; } + + public void OnNext(TSecond value) + { + lock (_parent._gate) + { + HasValue = true; + Value = value; + + if (_other.HasValue) + { + TResult res; + try + { + res = _parent._resultSelector(_other.Value!, value); + } + catch (Exception ex) + { + _parent.ForwardOnError(ex); + return; + } + + _parent.ForwardOnNext(res); + } + else if (_other.Done) + { + _parent.ForwardOnCompleted(); + } + } + } + + public void OnError(Exception error) + { + lock (_parent._gate) + { + _parent.ForwardOnError(error); + } + } + + public void OnCompleted() + { + lock (_parent._gate) + { + Done = true; + + if (_other.Done) + { + _parent.ForwardOnCompleted(); + } + else + { + _parent._secondDisposable.Dispose(); + } + } + } + } + } + } + + #endregion + + #region [3,16]-ary + + #region Helpers for n-ary overloads + + internal interface ICombineLatest + { + void Next(int index); + void Fail(Exception error); + void Done(int index); + } + + internal abstract class CombineLatestSink : IdentitySink, ICombineLatest + { + protected readonly object _gate; + + private bool _hasValueAll; + private readonly bool[] _hasValue; + private readonly bool[] _isDone; + + protected CombineLatestSink(int arity, IObserver observer) + : base(observer) + { + _gate = new object(); + + _hasValue = new bool[arity]; + _isDone = new bool[arity]; + } + + public void Next(int index) + { + if (!_hasValueAll) + { + _hasValue[index] = true; + + var hasValueAll = true; + foreach (var hasValue in _hasValue) + { + if (!hasValue) + { + hasValueAll = false; + break; + } + } + + _hasValueAll = hasValueAll; + } + + if (_hasValueAll) + { + TResult res; + try + { + res = GetResult(); + } + catch (Exception ex) + { + ForwardOnError(ex); + + return; + } + + ForwardOnNext(res); + } + else + { + var allOthersDone = true; + for (var i = 0; i < _isDone.Length; i++) + { + if (i != index && !_isDone[i]) + { + allOthersDone = false; + break; + } + } + + if (allOthersDone) + { + ForwardOnCompleted(); + } + } + } + + protected abstract TResult GetResult(); + + public void Fail(Exception error) + { + ForwardOnError(error); + } + + public void Done(int index) + { + _isDone[index] = true; + + var allDone = true; + foreach (var isDone in _isDone) + { + if (!isDone) + { + allDone = false; + break; + } + } + + if (allDone) + { + ForwardOnCompleted(); + } + } + } + + internal sealed class CombineLatestObserver : SafeObserver + { + private readonly object _gate; + private readonly ICombineLatest _parent; + private readonly int _index; + private T? _value; + + public CombineLatestObserver(object gate, ICombineLatest parent, int index) + { + _gate = gate; + _parent = parent; + _index = index; + } + + public T? Value => _value; + + public override void OnNext(T value) + { + lock (_gate) + { + _value = value; + _parent.Next(_index); + } + } + + public override void OnError(Exception error) + { + Dispose(); + + lock (_gate) + { + _parent.Fail(error); + } + } + + public override void OnCompleted() + { + Dispose(); + lock (_gate) + { + _parent.Done(_index); + } + } + } + + #endregion + + #endregion + + #region N-ary + + internal sealed class CombineLatest : Producer._> + { + private readonly IEnumerable> _sources; + private readonly Func, TResult> _resultSelector; + + public CombineLatest(IEnumerable> sources, Func, TResult> resultSelector) + { + _sources = sources; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_sources); + + internal sealed class _ : IdentitySink + { + private readonly object _gate = new(); + private readonly Func, TResult> _resultSelector; + + public _(Func, TResult> resultSelector, IObserver observer) + : base(observer) + { + _resultSelector = resultSelector; + + // NB: These will be set in Run before getting used. + _hasValue = null!; + _values = null!; + _isDone = null!; + _subscriptions = null!; + } + + private bool[] _hasValue; + private bool _hasValueAll; + private TSource[] _values; + private bool[] _isDone; + private IDisposable[] _subscriptions; + + public void Run(IEnumerable> sources) + { + var srcs = sources.ToArray(); + + var N = srcs.Length; + + _hasValue = new bool[N]; + _hasValueAll = false; + + _values = new TSource[N]; + + _isDone = new bool[N]; + + _subscriptions = new IDisposable[N]; + + for (var i = 0; i < N; i++) + { + var j = i; + + var o = new SourceObserver(this, j); + _subscriptions[j] = o; + + o.SetResource(srcs[j].SubscribeSafe(o)); + } + + SetUpstream(StableCompositeDisposable.CreateTrusted(_subscriptions)); + } + + private void OnNext(int index, TSource value) + { + lock (_gate) + { + _values[index] = value; + + _hasValue[index] = true; + + if (_hasValueAll || (_hasValueAll = _hasValue.All())) + { + TResult res; + try + { + res = _resultSelector(new ReadOnlyCollection(_values)); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + ForwardOnNext(res); + } + else if (_isDone.AllExcept(index)) + { + ForwardOnCompleted(); + } + } + } + + private new void OnError(Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + } + + private void OnCompleted(int index) + { + lock (_gate) + { + _isDone[index] = true; + + if (_isDone.All()) + { + ForwardOnCompleted(); + } + else + { + _subscriptions[index].Dispose(); + } + } + } + + private sealed class SourceObserver : SafeObserver + { + private readonly _ _parent; + private readonly int _index; + + public SourceObserver(_ parent, int index) + { + _parent = parent; + _index = index; + } + + public override void OnNext(TSource value) + { + _parent.OnNext(_index, value); + } + + public override void OnError(Exception error) + { + _parent.OnError(error); + } + + public override void OnCompleted() + { + _parent.OnCompleted(_index); + } + } + } + } + + #endregion +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Concat.cs b/LibExternal/System.Reactive/Linq/Observable/Concat.cs new file mode 100644 index 0000000..f596f54 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Concat.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Concat : Producer._>, IConcatenatable + { + private readonly IEnumerable> _sources; + + public Concat(IEnumerable> sources) + { + _sources = sources; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_sources); + + public IEnumerable> GetSources() => _sources; + + internal sealed class _ : ConcatSink + { + public _(IObserver observer) + : base(observer) + { + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/ConcatMany.cs b/LibExternal/System.Reactive/Linq/Observable/ConcatMany.cs new file mode 100644 index 0000000..843d746 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/ConcatMany.cs @@ -0,0 +1,222 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Concurrent; +using System.Reactive.Disposables; +using System.Threading; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class ConcatMany : IObservable + { + private readonly IObservable> _sources; + + internal ConcatMany(IObservable> sources) + { + _sources = sources; + } + + public IDisposable Subscribe(IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + var parent = new ConcatManyOuterObserver(observer); + + var d = _sources.SubscribeSafe(parent); + parent.OnSubscribe(d); + + return parent; + } + + internal sealed class ConcatManyOuterObserver : IObserver>, IDisposable + { + private readonly IObserver _downstream; + private readonly ConcurrentQueue> _queue; + private readonly InnerObserver _innerObserver; + + private SingleAssignmentDisposableValue _upstream; + private int _trampoline; + private Exception? _error; + private bool _done; + private int _active; + + internal ConcatManyOuterObserver(IObserver downstream) + { + _downstream = downstream; + _queue = new ConcurrentQueue>(); + _innerObserver = new InnerObserver(this); + } + + internal void OnSubscribe(IDisposable d) + { + _upstream.Disposable = d; + } + + public void Dispose() + { + _innerObserver.Dispose(); + DisposeMain(); + } + + private void DisposeMain() + { + _upstream.Dispose(); + } + + private bool IsDisposed() + { + return _upstream.IsDisposed; + } + + public void OnCompleted() + { + Volatile.Write(ref _done, true); + Drain(); + } + + public void OnError(Exception error) + { + if (Interlocked.CompareExchange(ref _error, error, null) == null) + { + Volatile.Write(ref _done, true); + Drain(); + } + } + + public void OnNext(IObservable value) + { + _queue.Enqueue(value); + Drain(); + } + + private void InnerNext(T item) + { + _downstream.OnNext(item); + } + + private void InnerError(Exception error) + { + if (_innerObserver.Finish()) + { + if (Interlocked.CompareExchange(ref _error, error, null) == null) + { + Volatile.Write(ref _done, true); + Volatile.Write(ref _active, 0); + Drain(); + } + } + } + + private void InnerComplete() + { + if (_innerObserver.Finish()) + { + Volatile.Write(ref _active, 0); + Drain(); + } + } + + private void Drain() + { + if (Interlocked.Increment(ref _trampoline) != 1) + { + return; + } + + do + { + if (IsDisposed()) + { + while (_queue.TryDequeue(out _)) + { + } + } + else + { + if (Volatile.Read(ref _active) == 0) + { + var isDone = Volatile.Read(ref _done); + + if (isDone) + { + var ex = Volatile.Read(ref _error); + if (ex != null) + { + _downstream.OnError(ex); + DisposeMain(); + continue; + } + } + + if (_queue.TryDequeue(out var source)) + { + var sad = new SingleAssignmentDisposable(); + if (_innerObserver.SetDisposable(sad)) + { + Interlocked.Exchange(ref _active, 1); + sad.Disposable = source.SubscribeSafe(_innerObserver); + } + } + else + { + if (isDone) + { + _downstream.OnCompleted(); + DisposeMain(); + } + } + } + } + } while (Interlocked.Decrement(ref _trampoline) != 0); + } + + internal sealed class InnerObserver : IObserver, IDisposable + { + private readonly ConcatManyOuterObserver _parent; + + internal IDisposable? Upstream; + + internal InnerObserver(ConcatManyOuterObserver parent) + { + _parent = parent; + } + + internal bool SetDisposable(SingleAssignmentDisposable sad) + { + return Disposable.TrySetSingle(ref Upstream, sad) == TrySetSingleResult.Success; + } + + internal bool Finish() + { + var sad = Volatile.Read(ref Upstream); + + if (sad != BooleanDisposable.True) + { + if (Interlocked.CompareExchange(ref Upstream, null, sad) == sad) + { + sad!.Dispose(); // NB: Cannot be null when we get here; SetDisposable is called before Inner[Error|Completed] calls Finish. + return true; + } + } + + return false; + } + + public void Dispose() + { + Disposable.Dispose(ref Upstream); + } + + public void OnCompleted() => _parent.InnerComplete(); + + public void OnError(Exception error) => _parent.InnerError(error); + + public void OnNext(T value) => _parent.InnerNext(value); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Contains.cs b/LibExternal/System.Reactive/Linq/Observable/Contains.cs new file mode 100644 index 0000000..009eca3 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Contains.cs @@ -0,0 +1,65 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Contains : Producer._> + { + private readonly IObservable _source; + private readonly TSource _value; + private readonly IEqualityComparer _comparer; + + public Contains(IObservable source, TSource value, IEqualityComparer comparer) + { + _source = source; + _value = value; + _comparer = comparer; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private readonly TSource _value; + private readonly IEqualityComparer _comparer; + + public _(Contains parent, IObserver observer) + : base(observer) + { + _value = parent._value; + _comparer = parent._comparer; + } + + public override void OnNext(TSource value) + { + bool res; + try + { + res = _comparer.Equals(value, _value); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + if (res) + { + ForwardOnNext(true); + ForwardOnCompleted(); + } + } + + public override void OnCompleted() + { + ForwardOnNext(false); + ForwardOnCompleted(); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Count.cs b/LibExternal/System.Reactive/Linq/Observable/Count.cs new file mode 100644 index 0000000..40dfe0d --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Count.cs @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class Count + { + internal sealed class All : Producer + { + private readonly IObservable _source; + + public All(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private int _count; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(TSource value) + { + try + { + checked + { + _count++; + } + } + catch (Exception ex) + { + ForwardOnError(ex); + } + } + + public override void OnCompleted() + { + ForwardOnNext(_count); + ForwardOnCompleted(); + } + } + } + + internal sealed class Predicate : Producer + { + private readonly IObservable _source; + private readonly Func _predicate; + + public Predicate(IObservable source, Func predicate) + { + _source = source; + _predicate = predicate; + } + + protected override _ CreateSink(IObserver observer) => new(_predicate, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private readonly Func _predicate; + private int _count; + + public _(Func predicate, IObserver observer) + : base(observer) + { + _predicate = predicate; + } + + public override void OnNext(TSource value) + { + try + { + checked + { + if (_predicate(value)) + { + _count++; + } + } + } + catch (Exception ex) + { + ForwardOnError(ex); + } + } + + public override void OnCompleted() + { + ForwardOnNext(_count); + ForwardOnCompleted(); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/DefaultIfEmpty.cs b/LibExternal/System.Reactive/Linq/Observable/DefaultIfEmpty.cs new file mode 100644 index 0000000..6ec0371 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/DefaultIfEmpty.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class DefaultIfEmpty : Producer._> + { + private readonly IObservable _source; + private readonly TSource _defaultValue; + + public DefaultIfEmpty(IObservable source, TSource defaultValue) + { + _source = source; + _defaultValue = defaultValue; + } + + protected override _ CreateSink(IObserver observer) => new(_defaultValue, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly TSource _defaultValue; + private bool _found; + + public _(TSource defaultValue, IObserver observer) + : base(observer) + { + _defaultValue = defaultValue; + _found = false; + } + + public override void OnNext(TSource value) + { + _found = true; + ForwardOnNext(value); + } + + public override void OnCompleted() + { + if (!_found) + { + ForwardOnNext(_defaultValue); + } + + ForwardOnCompleted(); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Defer.cs b/LibExternal/System.Reactive/Linq/Observable/Defer.cs new file mode 100644 index 0000000..1516562 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Defer.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Defer : Producer._>, IEvaluatableObservable + { + private readonly Func> _observableFactory; + + public Defer(Func> observableFactory) + { + _observableFactory = observableFactory; + } + + protected override _ CreateSink(IObserver observer) => new(_observableFactory, observer); + + protected override void Run(_ sink) => sink.Run(); + + public IObservable Eval() => _observableFactory(); + + internal sealed class _ : IdentitySink + { + private readonly Func> _observableFactory; + + public _(Func> observableFactory, IObserver observer) + : base(observer) + { + _observableFactory = observableFactory; + } + + public void Run() + { + IObservable result; + try + { + result = _observableFactory(); + } + catch (Exception exception) + { + ForwardOnError(exception); + + return; + } + + Run(result); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Delay.cs b/LibExternal/System.Reactive/Linq/Observable/Delay.cs new file mode 100644 index 0000000..1abebc0 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Delay.cs @@ -0,0 +1,831 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Concurrency; +using System.Reactive.Disposables; +using System.Threading; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class Delay + { + internal abstract class Base : Producer._> + where TParent : Base + { + protected readonly IObservable _source; + protected readonly IScheduler _scheduler; + + protected Base(IObservable source, IScheduler scheduler) + { + _source = source; + _scheduler = scheduler; + } + + internal abstract class _ : IdentitySink + { + protected readonly IScheduler _scheduler; + private IStopwatch? _watch; + + protected _(TParent parent, IObserver observer) + : base(observer) + { + _scheduler = parent._scheduler; + } + + public void Run(TParent parent) + { + _watch = _scheduler.StartStopwatch(); + + RunCore(parent); + + base.Run(parent._source); + } + + protected TimeSpan Elapsed => _watch!.Elapsed; // NB: Only used after Run is called. + + protected abstract void RunCore(TParent parent); + } + + internal abstract class S : _ + { + protected readonly object _gate = new(); + protected SerialDisposableValue _cancelable; + + protected S(TParent parent, IObserver observer) + : base(parent, observer) + { + } + + protected TimeSpan _delay; + protected bool _ready; + protected bool _active; + protected bool _running; + protected Queue> _queue = new(); + + private bool _hasCompleted; + private TimeSpan _completeAt; + private bool _hasFailed; + private Exception? _exception; + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _cancelable.Dispose(); + } + } + + public override void OnNext(TSource value) + { + var shouldRun = false; + + lock (_gate) + { + var next = Elapsed.Add(_delay); + + _queue.Enqueue(new Reactive.TimeInterval(value, next)); + + shouldRun = _ready && !_active; + _active = true; + } + + if (shouldRun) + { + DrainQueue(_delay); + } + } + + public override void OnError(Exception error) + { + DisposeUpstream(); + + var shouldRun = false; + + lock (_gate) + { + _queue.Clear(); + + _exception = error; + _hasFailed = true; + + shouldRun = !_running; + } + + if (shouldRun) + { + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + DisposeUpstream(); + + var shouldRun = false; + + lock (_gate) + { + var next = Elapsed.Add(_delay); + + _completeAt = next; + _hasCompleted = true; + + shouldRun = _ready && !_active; + _active = true; + } + + if (shouldRun) + { + DrainQueue(_delay); + } + } + + protected void DrainQueue(TimeSpan next) + { + _cancelable.Disposable = _scheduler.Schedule(this, next, static (@this, a) => @this.DrainQueue(a)); + } + + private void DrainQueue(Action recurse) + { + lock (_gate) + { + if (_hasFailed) + { + return; + } + + _running = true; + } + + // + // The shouldYield flag was added to address TFS 487881: "Delay can be unfair". In the old + // implementation, the loop below kept running while there was work for immediate dispatch, + // potentially causing a long running work item on the target scheduler. With the addition + // of long-running scheduling in Rx v2.0, we can check whether the scheduler supports this + // interface and perform different processing (see LongRunningImpl). To reduce the code + // churn in the old loop code here, we set the shouldYield flag to true after the first + // dispatch iteration, in order to break from the loop and enter the recursive scheduling path. + // + var shouldYield = false; + + while (true) + { + var hasFailed = false; + var error = default(Exception); + + var hasValue = false; + var value = default(TSource); + var hasCompleted = false; + + var shouldRecurse = false; + var recurseDueTime = default(TimeSpan); + + lock (_gate) + { + if (_hasFailed) + { + error = _exception; + hasFailed = true; + _running = false; + } + else + { + var now = Elapsed; + + if (_queue.Count > 0) + { + var nextDue = _queue.Peek().Interval; + + if (nextDue.CompareTo(now) <= 0 && !shouldYield) + { + value = _queue.Dequeue().Value; + hasValue = true; + } + else + { + shouldRecurse = true; + recurseDueTime = Scheduler.Normalize(nextDue.Subtract(now)); + _running = false; + } + } + else if (_hasCompleted) + { + if (_completeAt.CompareTo(now) <= 0 && !shouldYield) + { + hasCompleted = true; + } + else + { + shouldRecurse = true; + recurseDueTime = Scheduler.Normalize(_completeAt.Subtract(now)); + _running = false; + } + } + else + { + _running = false; + _active = false; + } + } + } /* lock (_gate) */ + + if (hasValue) + { + ForwardOnNext(value!); + shouldYield = true; + } + else + { + if (hasCompleted) + { + ForwardOnCompleted(); + } + else if (hasFailed) + { + ForwardOnError(error!); + } + else if (shouldRecurse) + { + recurse(this, recurseDueTime); + } + + return; + } + } /* while (true) */ + } + } + + protected abstract class L : _ + { + protected readonly object _gate = new(); + private readonly SemaphoreSlim _evt = new(0); + + protected L(TParent parent, IObserver observer) + : base(parent, observer) + { + } + + protected Queue> _queue = new(); + protected SerialDisposableValue _cancelable; + protected TimeSpan _delay; + + private bool _hasCompleted; + private TimeSpan _completeAt; + private bool _hasFailed; + private Exception? _exception; + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _cancelable.Dispose(); + } + } + + protected void ScheduleDrain() + { + var cd = new CancellationDisposable(); + _cancelable.Disposable = cd; + + _scheduler.AsLongRunning()!.ScheduleLongRunning(cd.Token, DrainQueue); // NB: This class is only used with long-running schedulers. + } + + public override void OnNext(TSource value) + { + lock (_gate) + { + var next = Elapsed.Add(_delay); + + _queue.Enqueue(new Reactive.TimeInterval(value, next)); + + _evt.Release(); + } + } + + public override void OnError(Exception error) + { + DisposeUpstream(); + + lock (_gate) + { + _queue.Clear(); + + _exception = error; + _hasFailed = true; + + _evt.Release(); + } + } + + public override void OnCompleted() + { + DisposeUpstream(); + + lock (_gate) + { + var next = Elapsed.Add(_delay); + + _completeAt = next; + _hasCompleted = true; + + _evt.Release(); + } + } + +#pragma warning disable CA1068 // (CancellationToken parameters must come last.) Method signature determined by ISchedulerLongRunning, so we can't comply with the analyzer rule here. + private void DrainQueue(CancellationToken token, ICancelable cancel) +#pragma warning restore CA1068 + { + while (true) + { + try + { + _evt.Wait(token); + } + catch (OperationCanceledException) + { + return; + } + + var hasFailed = false; + var error = default(Exception); + + var hasValue = false; + var value = default(TSource); + var hasCompleted = false; + + var shouldWait = false; + var waitTime = default(TimeSpan); + + lock (_gate) + { + if (_hasFailed) + { + error = _exception; + hasFailed = true; + } + else + { + var now = Elapsed; + + if (_queue.Count > 0) + { + var next = _queue.Dequeue(); + + hasValue = true; + value = next.Value; + + var nextDue = next.Interval; + if (nextDue.CompareTo(now) > 0) + { + shouldWait = true; + waitTime = Scheduler.Normalize(nextDue.Subtract(now)); + } + } + else if (_hasCompleted) + { + hasCompleted = true; + + if (_completeAt.CompareTo(now) > 0) + { + shouldWait = true; + waitTime = Scheduler.Normalize(_completeAt.Subtract(now)); + } + } + } + } /* lock (_gate) */ + + if (shouldWait) + { + var timer = new ManualResetEventSlim(); + _scheduler.ScheduleAction(timer, waitTime, static slimTimer => { slimTimer.Set(); }); + + try + { + timer.Wait(token); + } + catch (OperationCanceledException) + { + return; + } + } + + if (hasValue) + { + ForwardOnNext(value!); + } + else + { + if (hasCompleted) + { + ForwardOnCompleted(); + } + else if (hasFailed) + { + ForwardOnError(error!); + } + + return; + } + } + } + } + } + + internal sealed class Absolute : Base + { + private readonly DateTimeOffset _dueTime; + + public Absolute(IObservable source, DateTimeOffset dueTime, IScheduler scheduler) + : base(source, scheduler) + { + _dueTime = dueTime; + } + + protected override _ CreateSink(IObserver observer) => _scheduler.AsLongRunning() != null ? (_)new L(this, observer) : new S(this, observer); + + protected override void Run(_ sink) => sink.Run(this); + + private new sealed class S : Base.S + { + public S(Absolute parent, IObserver observer) + : base(parent, observer) + { + } + + protected override void RunCore(Absolute parent) + { + _ready = false; + + _cancelable.TrySetFirst(parent._scheduler.ScheduleAction(this, parent._dueTime, static @this => @this.Start())); + } + + private void Start() + { + var next = default(TimeSpan); + var shouldRun = false; + + lock (_gate) + { + _delay = Elapsed; + + var oldQueue = _queue; + _queue = new Queue>(); + + if (oldQueue.Count > 0) + { + next = oldQueue.Peek().Interval; + + while (oldQueue.Count > 0) + { + var item = oldQueue.Dequeue(); + _queue.Enqueue(new Reactive.TimeInterval(item.Value, item.Interval.Add(_delay))); + } + + shouldRun = true; + _active = true; + } + + _ready = true; + } + + if (shouldRun) + { + DrainQueue(next); + } + } + } + + private new sealed class L : Base.L + { + public L(Absolute parent, IObserver observer) + : base(parent, observer) + { + } + + protected override void RunCore(Absolute parent) + { + // ScheduleDrain might have already set a newer disposable + // using TrySetSerial would cancel it, stopping the emission + // and hang the consumer + _cancelable.TrySetFirst(parent._scheduler.ScheduleAction(this, parent._dueTime, static @this => @this.Start())); + } + + private void Start() + { + lock (_gate) + { + _delay = Elapsed; + + var oldQueue = _queue; + _queue = new Queue>(); + + while (oldQueue.Count > 0) + { + var item = oldQueue.Dequeue(); + _queue.Enqueue(new Reactive.TimeInterval(item.Value, item.Interval.Add(_delay))); + } + } + + ScheduleDrain(); + } + } + } + + internal sealed class Relative : Base + { + private readonly TimeSpan _dueTime; + + public Relative(IObservable source, TimeSpan dueTime, IScheduler scheduler) + : base(source, scheduler) + { + _dueTime = dueTime; + } + + protected override _ CreateSink(IObserver observer) => _scheduler.AsLongRunning() != null ? (_)new L(this, observer) : new S(this, observer); + + protected override void Run(_ sink) => sink.Run(this); + + private new sealed class S : Base.S + { + public S(Relative parent, IObserver observer) + : base(parent, observer) + { + } + + protected override void RunCore(Relative parent) + { + _ready = true; + + _delay = Scheduler.Normalize(parent._dueTime); + } + } + + private new sealed class L : Base.L + { + public L(Relative parent, IObserver observer) + : base(parent, observer) + { + } + + protected override void RunCore(Relative parent) + { + _delay = Scheduler.Normalize(parent._dueTime); + ScheduleDrain(); + } + } + } + } + + internal static class Delay + { + internal abstract class Base : Producer._> + where TParent : Base + { + protected readonly IObservable _source; + + protected Base(IObservable source) + { + _source = source; + } + + internal abstract class _ : IdentitySink + { + private readonly CompositeDisposable _delays = new(); + private readonly object _gate = new(); + + private readonly Func> _delaySelector; + + protected _(Func> delaySelector, IObserver observer) + : base(observer) + { + _delaySelector = delaySelector; + } + + private bool _atEnd; + private SingleAssignmentDisposableValue _subscription; + + public void Run(TParent parent) + { + _atEnd = false; + + _subscription.Disposable = RunCore(parent); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _subscription.Dispose(); + _delays.Dispose(); + } + base.Dispose(disposing); + } + + protected abstract IDisposable RunCore(TParent parent); + + public override void OnNext(TSource value) + { + IObservable delay; + try + { + delay = _delaySelector(value); + } + catch (Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + + return; + } + + var observer = new DelayObserver(this, value); + _delays.Add(observer); + observer.SetResource(delay.SubscribeSafe(observer)); + } + + public override void OnError(Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + lock (_gate) + { + _atEnd = true; + _subscription.Dispose(); + + CheckDone(); + } + } + + private void CheckDone() + { + if (_atEnd && _delays.Count == 0) + { + ForwardOnCompleted(); + } + } + + private sealed class DelayObserver : SafeObserver + { + private readonly _ _parent; + private readonly TSource _value; + private bool _once; + + public DelayObserver(_ parent, TSource value) + { + _parent = parent; + _value = value; + } + + public override void OnNext(TDelay value) + { + if (!_once) + { + _once = true; + lock (_parent._gate) + { + _parent.ForwardOnNext(_value); + + _parent._delays.Remove(this); + _parent.CheckDone(); + } + } + } + + public override void OnError(Exception error) + { + lock (_parent._gate) + { + _parent.ForwardOnError(error); + } + } + + public override void OnCompleted() + { + if (!_once) + { + lock (_parent._gate) + { + _parent.ForwardOnNext(_value); + + _parent._delays.Remove(this); + _parent.CheckDone(); + } + } + } + } + } + } + + internal class Selector : Base + { + private readonly Func> _delaySelector; + + public Selector(IObservable source, Func> delaySelector) + : base(source) + { + _delaySelector = delaySelector; + } + + protected override Base._ CreateSink(IObserver observer) => new _(_delaySelector, observer); + + protected override void Run(Base._ sink) => sink.Run(this); + + private new sealed class _ : Base._ + { + public _(Func> delaySelector, IObserver observer) + : base(delaySelector, observer) + { + } + + protected override IDisposable RunCore(Selector parent) => parent._source.SubscribeSafe(this); + } + } + + internal sealed class SelectorWithSubscriptionDelay : Base + { + private readonly IObservable _subscriptionDelay; + private readonly Func> _delaySelector; + + public SelectorWithSubscriptionDelay(IObservable source, IObservable subscriptionDelay, Func> delaySelector) + : base(source) + { + _subscriptionDelay = subscriptionDelay; + _delaySelector = delaySelector; + } + + protected override Base._ CreateSink(IObserver observer) => new _(_delaySelector, observer); + + protected override void Run(Base._ sink) => sink.Run(this); + + private new sealed class _ : Base._ + { + public _(Func> delaySelector, IObserver observer) + : base(delaySelector, observer) + { + } + + protected override IDisposable RunCore(SelectorWithSubscriptionDelay parent) + { + var delayConsumer = new SubscriptionDelayObserver(this, parent._source); + + delayConsumer.SetFirst(parent._subscriptionDelay.SubscribeSafe(delayConsumer)); + + return delayConsumer; + } + + private sealed class SubscriptionDelayObserver : IObserver, IDisposable + { + private readonly _ _parent; + private readonly IObservable _source; + private SerialDisposableValue _subscription; + + public SubscriptionDelayObserver(_ parent, IObservable source) + { + _parent = parent; + _source = source; + } + + internal void SetFirst(IDisposable d) + { + _subscription.TrySetFirst(d); + } + + public void OnNext(TDelay value) + { + _subscription.Disposable = _source.SubscribeSafe(_parent); + } + + public void OnError(Exception error) + { + _parent.ForwardOnError(error); + } + + public void OnCompleted() + { + _subscription.Disposable = _source.SubscribeSafe(_parent); + } + + public void Dispose() + { + _subscription.Dispose(); + } + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/DelaySubscription.cs b/LibExternal/System.Reactive/Linq/Observable/DelaySubscription.cs new file mode 100644 index 0000000..63c021d --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/DelaySubscription.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal abstract class DelaySubscription : Producer._> + { + private readonly IObservable _source; + private readonly IScheduler _scheduler; + + protected DelaySubscription(IObservable source, IScheduler scheduler) + { + _source = source; + _scheduler = scheduler; + } + + internal sealed class Relative : DelaySubscription + { + private readonly TimeSpan _dueTime; + + public Relative(IObservable source, TimeSpan dueTime, IScheduler scheduler) + : base(source, scheduler) + { + _dueTime = dueTime; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source, _scheduler, _dueTime); + } + + internal sealed class Absolute : DelaySubscription + { + private readonly DateTimeOffset _dueTime; + + public Absolute(IObservable source, DateTimeOffset dueTime, IScheduler scheduler) + : base(source, scheduler) + { + _dueTime = dueTime; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source, _scheduler, _dueTime); + } + + internal sealed class _ : IdentitySink + { + public _(IObserver observer) + : base(observer) + { + } + + public void Run(IObservable source, IScheduler scheduler, DateTimeOffset dueTime) + { + SetUpstream(scheduler.ScheduleAction((@this: this, source), dueTime, static tuple => tuple.source.SubscribeSafe(tuple.@this))); + } + + public void Run(IObservable source, IScheduler scheduler, TimeSpan dueTime) + { + SetUpstream(scheduler.ScheduleAction((@this: this, source), dueTime, static tuple => tuple.source.SubscribeSafe(tuple.@this))); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Dematerialize.cs b/LibExternal/System.Reactive/Linq/Observable/Dematerialize.cs new file mode 100644 index 0000000..e082e28 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Dematerialize.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Dematerialize : Producer._> + { + private readonly IObservable> _source; + + public Dematerialize(IObservable> source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink, TSource> + { + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(Notification value) + { + switch (value.Kind) + { + case NotificationKind.OnNext: + ForwardOnNext(value.Value); + break; + case NotificationKind.OnError: + ForwardOnError(value.Exception!); + break; + case NotificationKind.OnCompleted: + ForwardOnCompleted(); + break; + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Distinct.cs b/LibExternal/System.Reactive/Linq/Observable/Distinct.cs new file mode 100644 index 0000000..b1f7a1c --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Distinct.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Distinct : Producer._> + { + private readonly IObservable _source; + private readonly Func _keySelector; + private readonly IEqualityComparer _comparer; + + public Distinct(IObservable source, Func keySelector, IEqualityComparer comparer) + { + _source = source; + _keySelector = keySelector; + _comparer = comparer; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly Func _keySelector; + private readonly HashSet _hashSet; + + public _(Distinct parent, IObserver observer) + : base(observer) + { + _keySelector = parent._keySelector; + _hashSet = new HashSet(parent._comparer); + } + + public override void OnNext(TSource value) + { + TKey key; + bool hasAdded; + try + { + key = _keySelector(value); + hasAdded = _hashSet.Add(key); + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + if (hasAdded) + { + ForwardOnNext(value); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/DistinctUntilChanged.cs b/LibExternal/System.Reactive/Linq/Observable/DistinctUntilChanged.cs new file mode 100644 index 0000000..9541a6a --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/DistinctUntilChanged.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class DistinctUntilChanged : Producer._> + { + private readonly IObservable _source; + private readonly Func _keySelector; + private readonly IEqualityComparer _comparer; + + public DistinctUntilChanged(IObservable source, Func keySelector, IEqualityComparer comparer) + { + _source = source; + _keySelector = keySelector; + _comparer = comparer; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly Func _keySelector; + private readonly IEqualityComparer _comparer; + + private TKey? _currentKey; + private bool _hasCurrentKey; + + public _(DistinctUntilChanged parent, IObserver observer) + : base(observer) + { + _keySelector = parent._keySelector; + _comparer = parent._comparer; + } + + public override void OnNext(TSource value) + { + TKey key; + try + { + key = _keySelector(value); + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + var comparerEquals = false; + if (_hasCurrentKey) + { + try + { + comparerEquals = _comparer.Equals(_currentKey!, key); + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + } + + if (!_hasCurrentKey || !comparerEquals) + { + _hasCurrentKey = true; + _currentKey = key; + ForwardOnNext(value); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Do.cs b/LibExternal/System.Reactive/Linq/Observable/Do.cs new file mode 100644 index 0000000..45fcb89 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Do.cs @@ -0,0 +1,201 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class Do + { + internal sealed class OnNext : Producer + { + private readonly IObservable _source; + private readonly Action _onNext; + + public OnNext(IObservable source, Action onNext) + { + _source = source; + _onNext = onNext; + } + + protected override _ CreateSink(IObserver observer) => new(_onNext, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly Action _onNext; + + public _(Action onNext, IObserver observer) + : base(observer) + { + _onNext = onNext; + } + + public override void OnNext(TSource value) + { + try + { + _onNext(value); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + ForwardOnNext(value); + } + } + } + + internal sealed class Observer : Producer + { + private readonly IObservable _source; + private readonly IObserver _observer; + + public Observer(IObservable source, IObserver observer) + { + _source = source; + _observer = observer; + } + + protected override _ CreateSink(IObserver observer) => new(_observer, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly IObserver _doObserver; + + public _(IObserver doObserver, IObserver observer) + : base(observer) + { + _doObserver = doObserver; + } + + public override void OnNext(TSource value) + { + try + { + _doObserver.OnNext(value); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + ForwardOnNext(value); + } + + public override void OnError(Exception error) + { + try + { + _doObserver.OnError(error); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + ForwardOnError(error); + } + + public override void OnCompleted() + { + try + { + _doObserver.OnCompleted(); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + ForwardOnCompleted(); + } + } + } + + internal sealed class Actions : Producer + { + private readonly IObservable _source; + private readonly Action _onNext; + private readonly Action _onError; + private readonly Action _onCompleted; + + public Actions(IObservable source, Action onNext, Action onError, Action onCompleted) + { + _source = source; + _onNext = onNext; + _onError = onError; + _onCompleted = onCompleted; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + // CONSIDER: This sink has a parent reference that can be considered for removal. + + private readonly Actions _parent; + + public _(Actions parent, IObserver observer) + : base(observer) + { + _parent = parent; + } + + public override void OnNext(TSource value) + { + try + { + _parent._onNext(value); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + ForwardOnNext(value); + } + + public override void OnError(Exception error) + { + try + { + _parent._onError(error); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + ForwardOnError(error); + } + + public override void OnCompleted() + { + try + { + _parent._onCompleted(); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + ForwardOnCompleted(); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/DoWhile.cs b/LibExternal/System.Reactive/Linq/Observable/DoWhile.cs new file mode 100644 index 0000000..c6ff708 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/DoWhile.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class DoWhile : Producer._>, IConcatenatable + { + private readonly IObservable _source; + private readonly Func _condition; + + public DoWhile(IObservable source, Func condition) + { + _condition = condition; + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(GetSources()); + + public IEnumerable> GetSources() + { + yield return _source; + while (_condition()) + { + yield return _source; + } + } + + internal sealed class _ : ConcatSink + { + public _(IObserver observer) + : base(observer) + { + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/ElementAt.cs b/LibExternal/System.Reactive/Linq/Observable/ElementAt.cs new file mode 100644 index 0000000..e3cbf51 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/ElementAt.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class ElementAt : Producer._> + { + private readonly IObservable _source; + private readonly int _index; + + public ElementAt(IObservable source, int index) + { + _source = source; + _index = index; + } + + protected override _ CreateSink(IObserver observer) => new(_index, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private int _i; + + public _(int index, IObserver observer) + : base(observer) + { + _i = index; + } + + public override void OnNext(TSource value) + { + if (_i == 0) + { + ForwardOnNext(value); + ForwardOnCompleted(); + } + + _i--; + } + + public override void OnCompleted() + { + if (_i >= 0) + { + try + { + throw new ArgumentOutOfRangeException("index"); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/ElementAtOrDefault.cs b/LibExternal/System.Reactive/Linq/Observable/ElementAtOrDefault.cs new file mode 100644 index 0000000..5050f8a --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/ElementAtOrDefault.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class ElementAtOrDefault : Producer._> + { + private readonly IObservable _source; + private readonly int _index; + + public ElementAtOrDefault(IObservable source, int index) + { + _source = source; + _index = index; + } + + protected override _ CreateSink(IObserver observer) => new(_index, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private int _i; + + public _(int index, IObserver observer) + : base(observer) + { + _i = index; + } + + public override void OnNext(TSource value) + { + if (_i == 0) + { + ForwardOnNext(value); + ForwardOnCompleted(); + } + + _i--; + } + + public override void OnCompleted() + { + ForwardOnNext(default); + ForwardOnCompleted(); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Empty.cs b/LibExternal/System.Reactive/Linq/Observable/Empty.cs new file mode 100644 index 0000000..3a8233a --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Empty.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Empty : Producer._> + { + private readonly IScheduler _scheduler; + + public Empty(IScheduler scheduler) + { + _scheduler = scheduler; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_scheduler); + + internal sealed class _ : IdentitySink + { + public _(IObserver observer) + : base(observer) + { + } + + public void Run(IScheduler scheduler) + { + SetUpstream(scheduler.ScheduleAction(this, static target => target.OnCompleted())); + } + } + } + + internal sealed class EmptyDirect : BasicProducer + { + internal static readonly IObservable Instance = new EmptyDirect(); + + private EmptyDirect() { } + + protected override IDisposable Run(IObserver observer) + { + observer.OnCompleted(); + return Disposable.Empty; + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Finally.cs b/LibExternal/System.Reactive/Linq/Observable/Finally.cs new file mode 100644 index 0000000..c41a732 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Finally.cs @@ -0,0 +1,78 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; +using System.Threading; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Finally : Producer._> + { + private readonly IObservable _source; + private readonly Action _finallyAction; + + public Finally(IObservable source, Action finallyAction) + { + _source = source; + _finallyAction = finallyAction; + } + + protected override _ CreateSink(IObserver observer) => new(_finallyAction, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly Action _finallyAction; + private IDisposable? _sourceDisposable; + + public _(Action finallyAction, IObserver observer) + : base(observer) + { + _finallyAction = finallyAction; + } + + public override void Run(IObservable source) + { + var d = source.SubscribeSafe(this); + + if (Interlocked.CompareExchange(ref _sourceDisposable, d, null) == BooleanDisposable.True) + { + // The Dispose(bool) methode was already called before the + // subscription could be assign, hence the subscription + // needs to be diposed here and the action needs to be invoked. + try + { + d.Dispose(); + } + finally + { + _finallyAction(); + } + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + var d = Interlocked.Exchange(ref _sourceDisposable, BooleanDisposable.True); + if (d != BooleanDisposable.True && d != null) + { + try + { + d.Dispose(); + } + finally + { + _finallyAction(); + } + } + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/FirstAsync.cs b/LibExternal/System.Reactive/Linq/Observable/FirstAsync.cs new file mode 100644 index 0000000..e541180 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/FirstAsync.cs @@ -0,0 +1,120 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class FirstAsync + { + internal sealed class Sequence : Producer + { + private readonly IObservable _source; + + public Sequence(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private bool _found; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(TSource value) + { + _found = true; + ForwardOnNext(value); + ForwardOnCompleted(); + } + + public override void OnCompleted() + { + if (!_found) + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + } + } + } + + internal sealed class Predicate : Producer + { + private readonly IObservable _source; + private readonly Func _predicate; + + public Predicate(IObservable source, Func predicate) + { + _source = source; + _predicate = predicate; + } + + protected override _ CreateSink(IObserver observer) => new(_predicate, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly Func _predicate; + private bool _found; + + public _(Func predicate, IObserver observer) + : base(observer) + { + _predicate = predicate; + } + + public override void OnNext(TSource value) + { + bool b; + + try + { + b = _predicate(value); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + if (b) + { + _found = true; + ForwardOnNext(value); + ForwardOnCompleted(); + } + } + + public override void OnCompleted() + { + if (!_found) + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_MATCHING_ELEMENTS); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/FirstLastBlocking.cs b/LibExternal/System.Reactive/Linq/Observable/FirstLastBlocking.cs new file mode 100644 index 0000000..b21873d --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/FirstLastBlocking.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Threading; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal abstract class BaseBlocking : ManualResetEventSlim, IObserver + { + internal T? _value; + internal bool _hasValue; + internal Exception? _error; + + internal BaseBlocking() { } + + public void OnCompleted() + { + Set(); + } + + public void OnError(Exception error) + { + _value = default; + _error = error; + Set(); + } + + public abstract void OnNext(T value); + } + + internal sealed class FirstBlocking : BaseBlocking + { + public override void OnNext(T value) + { + if (!_hasValue) + { + _value = value; + _hasValue = true; + Set(); + } + } + } + + internal sealed class LastBlocking : BaseBlocking + { + public override void OnNext(T value) + { + _value = value; + _hasValue = true; + } + } + + internal sealed class SingleBlocking : BaseBlocking + { + internal bool _hasMoreThanOneElement; + + public override void OnNext(T value) + { + if (_hasValue) + { + _hasMoreThanOneElement = true; + Set(); + } + + _value = value; + _hasValue = true; + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/FirstOrDefaultAsync.cs b/LibExternal/System.Reactive/Linq/Observable/FirstOrDefaultAsync.cs new file mode 100644 index 0000000..ec51b09 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/FirstOrDefaultAsync.cs @@ -0,0 +1,97 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class FirstOrDefaultAsync + { + internal sealed class Sequence : Producer + { + private readonly IObservable _source; + + public Sequence(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(TSource value) + { + ForwardOnNext(value); + ForwardOnCompleted(); + } + + public override void OnCompleted() + { + ForwardOnNext(default); + ForwardOnCompleted(); + } + } + } + + internal sealed class Predicate : Producer + { + private readonly IObservable _source; + private readonly Func _predicate; + + public Predicate(IObservable source, Func predicate) + { + _source = source; + _predicate = predicate; + } + + protected override _ CreateSink(IObserver observer) => new(_predicate, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private readonly Func _predicate; + + public _(Func predicate, IObserver observer) + : base(observer) + { + _predicate = predicate; + } + + public override void OnNext(TSource value) + { + bool b; + + try + { + b = _predicate(value); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + if (b) + { + ForwardOnNext(value); + ForwardOnCompleted(); + } + } + + public override void OnCompleted() + { + ForwardOnNext(default); + ForwardOnCompleted(); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/For.cs b/LibExternal/System.Reactive/Linq/Observable/For.cs new file mode 100644 index 0000000..6449cc8 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/For.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class For : Producer._>, IConcatenatable + { + private readonly IEnumerable _source; + private readonly Func> _resultSelector; + + public For(IEnumerable source, Func> resultSelector) + { + _source = source; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(GetSources()); + + public IEnumerable> GetSources() + { + foreach (var item in _source) + { + yield return _resultSelector(item); + } + } + + internal sealed class _ : ConcatSink + { + public _(IObserver observer) + : base(observer) + { + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/ForEach.cs b/LibExternal/System.Reactive/Linq/Observable/ForEach.cs new file mode 100644 index 0000000..16f2252 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/ForEach.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Threading; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class ForEach + { + public abstract class ObserverBase : ManualResetEventSlim, IObserver + { + private Exception? _exception; + private int _stopped; + + public Exception? Error => _exception; + + protected abstract void OnNextCore(TSource value); + + public void OnNext(TSource value) + { + if (Volatile.Read(ref _stopped) == 0) + { + try + { + OnNextCore(value); + } + catch (Exception ex) + { + OnError(ex); + } + } + } + + public void OnError(Exception error) + { + if (Interlocked.Exchange(ref _stopped, 1) == 0) + { + _exception = error; + Set(); + } + } + + public void OnCompleted() + { + if (Interlocked.Exchange(ref _stopped, 1) == 0) + { + Set(); + } + } + } + + public sealed class Observer : ObserverBase + { + private readonly Action _onNext; + + public Observer(Action onNext) => _onNext = onNext; + + protected override void OnNextCore(TSource value) => _onNext(value); + } + + public sealed class ObserverIndexed : ObserverBase + { + private readonly Action _onNext; + + private int _index; + + public ObserverIndexed(Action onNext) => _onNext = onNext; + + protected override void OnNextCore(TSource value) => _onNext(value, checked(_index++)); + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/FromEvent.cs b/LibExternal/System.Reactive/Linq/Observable/FromEvent.cs new file mode 100644 index 0000000..4d7ad28 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/FromEvent.cs @@ -0,0 +1,363 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reactive.Disposables; +using System.Reactive.Subjects; + +// +// BREAKING CHANGE v2 > v1.x - FromEvent[Pattern] now has an implicit SubscribeOn and Publish operation. +// +// The free-threaded nature of Rx is key to the performance characteristics of the event processing +// pipeline. However, in places where we bridge with the external world, this sometimes has negative +// effects due to thread-affine operations involved. The FromEvent[Pattern] bridges are one such +// place where we reach out to add and remove operations on events. +// +// Consider the following piece of code, assuming Rx v1.x usage: +// +// var txt = Observable.FromEventPattern(txtInput, "TextChanged"); +// var res = from term in txt +// from word in svc.Lookup(term).TakeUntil(txt) +// select word; +// +// This code is flawed for various reasons. Seasoned Rx developers will immediately suggest usage of +// the Publish operator to share the side-effects of subscribing to the txt sequence, resulting in +// only one subscription to the event: +// +// var txt = Observable.FromEventPattern(txtInput, "TextChanged"); +// var res = txt.Publish(txt_ => from term in txt_ +// from word in svc.Lookup(term).TakeUntil(txt_) +// select word); +// +// Customers are typically confused as to why FromEvent[Pattern] causes multiple handlers to be added +// to the underlying event. This is in contrast with other From* bridges which involve the use of a +// subject (e.g. FromAsyncPattern, FromAsync, and ToObservable on Task). +// +// But there are more issues with the code fragment above. Upon completion of the svc.Lookup(term) +// sequence, TakeUntil will unsubscribe from both sequences, causing the unsubscription to happen in +// the context of the source's OnCompleted, which may be the thread pool. Some thread-affine events +// don't quite like this. In UI frameworks like WPF and Silverlight, this turns out to be not much of +// a problem typically, but it's merely an accident things work out. From an e-mail conversion with +// the WPF/SL/Jupiter experts: +// +// "Unfortunately, as I expected, it’s confusing, and implementation details are showing through. +// The bottom line is that event add/remove should always be done on the right thread. +// +// Where events are implemented with compiler-generated code, i.e. MultiCastDelegate, the add/remove +// will be thread safe/agile. Where events are implemented in custom code, across Wpf/SL/WP/Jupiter, +// the add/remove are expected to happen on the Dispatcher thread. +// +// Jupiter actually has the consistent story here, where all the event add/remove implementations do +// the thread check. It should still be a “wrong thread” error, though, not an AV. +// +// In SL there’s a mix of core events (which do the thread check) and framework events (which use +// compiler-generated event implementations). So you get an exception if you unhook Button.Loaded +// from off thread, but you don’t get an exception if you unhook Button.Click. +// +// In WPF there’s a similar mix (some events are compiler-generated and some use the EventHandlerStore). +// But I don’t see any thread safety or thread check in the EventHandlerStore. So while it works, IIUC, +// it should have race conditions and corruptions." +// +// Starting with "Jupiter" (Windows XAML aka "Metro"), checks are added to ensure the add and remove +// operations for UI events are called from the UI thread. As a result, the dictionary suggest sample +// code shown above starts to fail. A possible fix is to use SubscribeOnDispatcher: +// +// var txt = Observable.FromEventPattern(txtInput, "TextChanged").SubscribeOnDispatcher(); +// var res = from term in txt +// from word in svc.Lookup(term).TakeUntil(txt) +// select word; +// +// This fix has two problems: +// +// 1. Customers often don't quite understand the difference between ObserveOn and SubscribeOn. In fact, +// we've given guidance that use of the latter is typically indicative of a misunderstanding, and +// is used rarely. Also, the fragment above would likely be extended with some UI binding code where +// one needs to use ObserveOnDispatcher, so the combination of both becomes even more confusing. +// +// 2. There's a subtle race condition now. Upon receiving a new term from the txt sequence, SelectMany's +// invocation of the result selector involves TakeUntil subscribing to txt again. However, the use +// of SubscribeOnDispatcher means the subscription is now happening asynchronously, leaving a time +// gap between returning from Subscribe and doing the += on the underlying event: +// +// (Subscription of TakeUntil to txt) +// | +// v +// txt -------------------------------------------------------------- +// | +// +-----...----+ (SubscribeOnDispatcher's post of Subscribe) +// | +// TextChanged ------"re"---------"rea"-------------"reac"-----"react"----... +// ^ +// | +// (where += on the event happens) +// +// While this problem is rare and sometimes gets mitigated by accident because code is posting back +// to e.g. the UI message loop, it's extremely hard to debug when things go wrong. +// +// In order to fix this behavior such that code has the expected behavior, we do two things in Rx v2.0: +// +// - To solve the cross-thread add/remove handler operations and make them single-thread affine, we +// now do an implicit SubscribeOn with the SynchronizationContext.Current retrieved eagerly upon +// calling FromEvent[Pattern]. This goes hand-in-hand with a recommendation: +// +// "Always call FromEvent[Pattern] in a place where you'd normally write += and -= operations +// yourself. Don't inline the creation of a FromEvent[Pattern] object inside a query." +// +// This recommendation helps to keep code clean (bridging operations are moved outside queries) and +// ensures the captured SynchronizationContext is the least surprising one. E.g in the sample code +// above, the whole query likely lives in a button_Click handler or so. +// +// - To solve the time gap issue, we now add implicit Publish behavior with ref-counted behavior. In +// other words, the new FromEvent[Pattern] is pretty much the same as: +// +// Observable_v2.FromEvent[Pattern]() +// == +// Observable_v1.FromEvent[Pattern]().SubscribeOn(SynchronizationContext.Current) +// .Publish() +// .RefCount() +// +// Overloads to FromEvent[Pattern] allow to specify the scheduler used for the SubscribeOn operation +// that's taking place internally. When omitted, a SynchronizationContextScheduler will be supplied +// if a current SynchronizationContext is found. If no current SynchronizationContext is found, the +// default scheduler is the immediate scheduler, falling back to the free-threaded behavior we had +// before in v1.x. (See GetSchedulerForCurrentContext in QueryLanguage.Events.cs). +// +// Notice a time gap can still occur at the point of the first subscription to the event sequence, +// or when the ref count fell back to zero. In cases of nested uses of the sequence (such as in the +// running example here), this is fine because the top-level subscription is kept alive for the whole +// duration. In other cases, there's already a race condition between the underlying event and the +// observable wrapper (assuming events are hot). For cold events that have side-effects upon add and +// remove handler operations, use of Observable.Create is recommended. This should be rather rare, +// as most events follow the typical MulticastDelegate implementation pattern: +// +// public event EventHandler Bar; +// +// protected void OnBar(int value) +// { +// var bar = Bar; +// if (bar != null) +// bar(this, new BarEventArgs(value)); +// } +// +// In here, there's already a race between the user hooking up an event handler through the += add +// operation and the event producer (possibly on a different thread) calling OnBar. It's also worth +// pointing out that this race condition is mitigated by a check in SynchronizationContextScheduler +// causing synchronous execution in case the caller is already on the target SynchronizationContext. +// This situation is common when using FromEvent[Pattern] immediately after declaring it, e.g. in +// the context of a UI event handler. +// +// Finally, notice we can't simply connect the event to a Subject upon a FromEvent[Pattern] call, +// because this would make it impossible to get rid of this one event handler (unless we expose some +// other means of resource maintenance, e.g. by making the returned object implement IDisposable). +// Also, this would cause the event producer to see the event's delegate in a non-null state all the +// time, causing event argument objects to be newed up, possibly sending those into a zero-observer +// subject (which is opaque to the event producer). Not to mention that the subject would always be +// rooted by the target event (even when the FromEvent[Pattern] observable wrapper is unreachable). +// +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class FromEvent : ClassicEventProducer + { + private readonly Func, TDelegate>? _conversion; + + public FromEvent(Action addHandler, Action removeHandler, IScheduler scheduler) + : base(addHandler, removeHandler, scheduler) + { + } + + public FromEvent(Func, TDelegate> conversion, Action addHandler, Action removeHandler, IScheduler scheduler) + : base(addHandler, removeHandler, scheduler) + { + _conversion = conversion; + } + + protected override TDelegate GetHandler(Action onNext) + { + TDelegate handler; + + if (_conversion == null) + { + handler = ReflectionUtils.CreateDelegate(onNext, typeof(Action).GetMethod(nameof(Action.Invoke))!); + } + else + { + handler = _conversion(onNext); + } + + return handler; + } + } + + internal abstract class EventProducer : BasicProducer + { + private readonly IScheduler _scheduler; + private readonly object _gate; + + protected EventProducer(IScheduler scheduler) + { + _scheduler = scheduler; + _gate = new object(); + } + + protected abstract TDelegate GetHandler(Action onNext); + protected abstract IDisposable AddHandler(TDelegate handler); + + private Session? _session; + + protected override IDisposable Run(IObserver observer) + { + IDisposable connection; + + lock (_gate) + { + // + // A session object holds on to a single handler to the underlying event, feeding + // into a subject. It also ref counts the number of connections to the subject. + // + // When the ref count goes back to zero, the event handler is unregistered, and + // the session will reach out to reset the _session field to null under the _gate + // lock. Future subscriptions will cause a new session to be created. + // + _session ??= new Session(this); + + connection = _session.Connect(observer); + } + + return connection; + } + + private sealed class Session + { + private readonly EventProducer _parent; + private readonly Subject _subject; + private readonly SingleAssignmentDisposable _removeHandler = new(); + + private int _count; + + public Session(EventProducer parent) + { + _parent = parent; + _subject = new Subject(); + } + + public IDisposable Connect(IObserver observer) + { + /* + * CALLERS - Ensure this is called under the lock! + * + lock (_parent._gate) */ + { + // + // We connect the given observer to the subject first, before performing any kind + // of initialization which will register an event handler. This is done to ensure + // we don't have a time gap between adding the handler and connecting the user's + // subject, e.g. when the ImmediateScheduler is used. + // + // [OK] Use of unsafe Subscribe: called on a known subject implementation. + // + var connection = _subject.Subscribe/*Unsafe*/(observer); + + if (++_count == 1) + { + try + { + Initialize(); + } + catch (Exception exception) + { + --_count; + connection.Dispose(); + + observer.OnError(exception); + return Disposable.Empty; + } + } + + return Disposable.Create( + (this, _parent, connection), + tuple => + { + var (@this, closureParent, closureConnection) = tuple; + + closureConnection.Dispose(); + + lock (closureParent._gate) + { + if (--@this._count == 0) + { + closureParent._scheduler.ScheduleAction(@this._removeHandler, static handler => handler.Dispose()); + closureParent._session = null; + } + } + }); + } + + } + + private void Initialize() + { + /* + * CALLERS - Ensure this is called under the lock! + * + lock (_parent._gate) */ + { + // + // Conversion code is supposed to be a pure function and shouldn't be run on the + // scheduler, but the add handler call should. Notice the scheduler can be the + // ImmediateScheduler, causing synchronous invocation. This is the default when + // no SynchronizationContext is found (see QueryLanguage.Events.cs and search for + // the GetSchedulerForCurrentContext method). + // + var onNext = _parent.GetHandler(_subject.OnNext); + _parent._scheduler.ScheduleAction(onNext, AddHandler); + } + } + + private void AddHandler(TDelegate onNext) + { + IDisposable removeHandler; + try + { + removeHandler = _parent.AddHandler(onNext); + } + catch (Exception exception) + { + _subject.OnError(exception); + return; + } + + // + // We don't propagate the exception to the OnError channel upon Dispose. This is + // not possible at this stage, because we've already auto-detached in the base + // class Producer implementation. Even if we would switch the OnError and auto- + // detach calls, it wouldn't work because the remove handler logic is scheduled + // on the given scheduler, causing asynchrony. We can't block waiting for the + // remove handler to run on the scheduler. + // + _removeHandler.Disposable = removeHandler; + } + } + } + + internal abstract class ClassicEventProducer : EventProducer + { + private readonly Action _addHandler; + private readonly Action _removeHandler; + + protected ClassicEventProducer(Action addHandler, Action removeHandler, IScheduler scheduler) + : base(scheduler) + { + _addHandler = addHandler; + _removeHandler = removeHandler; + } + + protected override IDisposable AddHandler(TDelegate handler) + { + _addHandler(handler); + return Disposable.Create( + (_removeHandler, handler), + static tuple => tuple._removeHandler(tuple.handler)); + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/FromEventPattern.cs b/LibExternal/System.Reactive/Linq/Observable/FromEventPattern.cs new file mode 100644 index 0000000..f5b6110 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/FromEventPattern.cs @@ -0,0 +1,149 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reflection; +using System.Threading; + +// +// BREAKING CHANGE v2 > v1.x - FromEvent[Pattern] now has an implicit SubscribeOn and Publish operation. +// +// See FromEvent.cs for more information. +// +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class FromEventPattern + { + public sealed class Impl : ClassicEventProducer> + { + private readonly Func, TDelegate>? _conversion; + + public Impl(Action addHandler, Action removeHandler, IScheduler scheduler) + : base(addHandler, removeHandler, scheduler) + { + } + + public Impl(Func, TDelegate> conversion, Action addHandler, Action removeHandler, IScheduler scheduler) + : base(addHandler, removeHandler, scheduler) + { + _conversion = conversion; + } + + protected override TDelegate GetHandler(Action> onNext) + { + TDelegate handler; + + if (_conversion == null) + { + Action h = (sender, eventArgs) => onNext(new EventPattern(sender, eventArgs)); + handler = ReflectionUtils.CreateDelegate(h, typeof(Action).GetMethod(nameof(Action.Invoke))!); + } + else + { + handler = _conversion((sender, eventArgs) => onNext(new EventPattern(sender, eventArgs))); + } + + return handler; + } + } + + public sealed class Impl : ClassicEventProducer> + { + public Impl(Action addHandler, Action removeHandler, IScheduler scheduler) + : base(addHandler, removeHandler, scheduler) + { + } + + protected override TDelegate GetHandler(Action> onNext) + { + Action h = (sender, eventArgs) => onNext(new EventPattern(sender, eventArgs)); + return ReflectionUtils.CreateDelegate(h, typeof(Action).GetMethod(nameof(Action.Invoke))!); + } + } + + public sealed class Handler : EventProducer + { + private readonly object? _target; + private readonly Type _delegateType; + private readonly MethodInfo _addMethod; + private readonly MethodInfo _removeMethod; + private readonly Func _getResult; + private readonly bool _isWinRT; + + public Handler(object? target, Type delegateType, MethodInfo addMethod, MethodInfo removeMethod, Func getResult, bool isWinRT, IScheduler scheduler) + : base(scheduler) + { + _isWinRT = isWinRT; + _target = target; + _delegateType = delegateType; + _addMethod = addMethod; + _removeMethod = removeMethod; + _getResult = getResult; + } + + protected override Delegate GetHandler(Action onNext) + { + Action h = (sender, eventArgs) => onNext(_getResult(sender, eventArgs)); + return ReflectionUtils.CreateDelegate(_delegateType, h, typeof(Action).GetMethod(nameof(Action.Invoke))!); + } + + protected override IDisposable AddHandler(Delegate handler) + { + Action removeHandler; + + try + { + if (_isWinRT) + { + removeHandler = AddHandlerCoreWinRT(handler); + } + else + { + removeHandler = AddHandlerCore(handler); + } + } + catch (TargetInvocationException tie) when (tie.InnerException != null) + { + throw tie.InnerException; + } + + return new RemoveHandlerDisposable(removeHandler); + } + + private sealed class RemoveHandlerDisposable : IDisposable + { + private Action? _removeHandler; + + public RemoveHandlerDisposable(Action removeHandler) + { + Volatile.Write(ref _removeHandler, removeHandler); + } + + public void Dispose() + { + try + { + Interlocked.Exchange(ref _removeHandler, null)?.Invoke(); + } + catch (TargetInvocationException tie) when (tie.InnerException != null) + { + throw tie.InnerException; + } + } + } + + private Action AddHandlerCore(Delegate handler) + { + _addMethod.Invoke(_target, new object[] { handler }); + return () => _removeMethod.Invoke(_target, new object[] { handler }); + } + + private Action AddHandlerCoreWinRT(Delegate handler) + { + var token = _addMethod.Invoke(_target, new object[] { handler }); + return () => _removeMethod.Invoke(_target, new[] { token }); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Generate.cs b/LibExternal/System.Reactive/Linq/Observable/Generate.cs new file mode 100644 index 0000000..0bfa8a4 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Generate.cs @@ -0,0 +1,373 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class Generate + { + internal sealed class NoTime : Producer + { + private readonly TState _initialState; + private readonly Func _condition; + private readonly Func _iterate; + private readonly Func _resultSelector; + private readonly IScheduler _scheduler; + + public NoTime(TState initialState, Func condition, Func iterate, Func resultSelector, IScheduler scheduler) + { + _initialState = initialState; + _condition = condition; + _iterate = iterate; + _resultSelector = resultSelector; + _scheduler = scheduler; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_scheduler); + + internal sealed class _ : IdentitySink + { + private readonly Func _condition; + private readonly Func _iterate; + private readonly Func _resultSelector; + + public _(NoTime parent, IObserver observer) + : base(observer) + { + _condition = parent._condition; + _iterate = parent._iterate; + _resultSelector = parent._resultSelector; + + _state = parent._initialState; + _first = true; + } + + private TState _state; + private bool _first; + + public void Run(IScheduler scheduler) + { + var longRunning = scheduler.AsLongRunning(); + if (longRunning != null) + { + SetUpstream(longRunning.ScheduleLongRunning(this, static (@this, c) => @this.Loop(c))); + } + else + { + SetUpstream(scheduler.Schedule(this, static (@this, a) => @this.LoopRec(a))); + } + } + + private void Loop(ICancelable cancel) + { + while (!cancel.IsDisposed) + { + bool hasResult; + var result = default(TResult); + + try + { + if (_first) + { + _first = false; + } + else + { + _state = _iterate(_state); + } + + hasResult = _condition(_state); + + if (hasResult) + { + result = _resultSelector(_state); + } + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + if (hasResult) + { + ForwardOnNext(result!); + } + else + { + break; + } + } + + if (!cancel.IsDisposed) + { + ForwardOnCompleted(); + } + } + + private void LoopRec(Action<_> recurse) + { + bool hasResult; + var result = default(TResult); + + try + { + if (_first) + { + _first = false; + } + else + { + _state = _iterate(_state); + } + + hasResult = _condition(_state); + + if (hasResult) + { + result = _resultSelector(_state); + } + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + if (hasResult) + { + ForwardOnNext(result!); + recurse(this); + } + else + { + ForwardOnCompleted(); + } + } + } + } + + internal sealed class Absolute : Producer + { + private readonly TState _initialState; + private readonly Func _condition; + private readonly Func _iterate; + private readonly Func _resultSelector; + private readonly Func _timeSelector; + private readonly IScheduler _scheduler; + + public Absolute(TState initialState, Func condition, Func iterate, Func resultSelector, Func timeSelector, IScheduler scheduler) + { + _initialState = initialState; + _condition = condition; + _iterate = iterate; + _resultSelector = resultSelector; + _timeSelector = timeSelector; + _scheduler = scheduler; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_scheduler, _initialState); + + internal sealed class _ : IdentitySink + { + private readonly Func _condition; + private readonly Func _iterate; + private readonly Func _resultSelector; + private readonly Func _timeSelector; + + public _(Absolute parent, IObserver observer) + : base(observer) + { + _condition = parent._condition; + _iterate = parent._iterate; + _resultSelector = parent._resultSelector; + _timeSelector = parent._timeSelector; + + _first = true; + } + + private bool _first; + private bool _hasResult; + private TResult? _result; + + private MultipleAssignmentDisposableValue _timerDisposable; + + public void Run(IScheduler outerScheduler, TState initialState) + { + var timer = new SingleAssignmentDisposable(); + _timerDisposable.Disposable = timer; + timer.Disposable = outerScheduler.Schedule((@this: this, initialState), static (scheduler, tuple) => tuple.@this.InvokeRec(scheduler, tuple.initialState)); + } + + protected override void Dispose(bool disposing) + { + _timerDisposable.Dispose(); + base.Dispose(disposing); + } + + private IDisposable InvokeRec(IScheduler self, TState state) + { + if (_hasResult) + { + ForwardOnNext(_result!); + } + + var time = default(DateTimeOffset); + + try + { + if (_first) + { + _first = false; + } + else + { + state = _iterate(state); + } + + _hasResult = _condition(state); + + if (_hasResult) + { + _result = _resultSelector(state); + time = _timeSelector(state); + } + } + catch (Exception exception) + { + ForwardOnError(exception); + return Disposable.Empty; + } + + if (!_hasResult) + { + ForwardOnCompleted(); + return Disposable.Empty; + } + + var timer = new SingleAssignmentDisposable(); + _timerDisposable.Disposable = timer; + timer.Disposable = self.Schedule((@this: this, state), time, static (scheduler, tuple) => tuple.@this.InvokeRec(scheduler, tuple.state)); + + return Disposable.Empty; + } + } + } + + internal sealed class Relative : Producer + { + private readonly TState _initialState; + private readonly Func _condition; + private readonly Func _iterate; + private readonly Func _resultSelector; + private readonly Func _timeSelector; + private readonly IScheduler _scheduler; + + public Relative(TState initialState, Func condition, Func iterate, Func resultSelector, Func timeSelector, IScheduler scheduler) + { + _initialState = initialState; + _condition = condition; + _iterate = iterate; + _resultSelector = resultSelector; + _timeSelector = timeSelector; + _scheduler = scheduler; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_scheduler, _initialState); + + internal sealed class _ : IdentitySink + { + private readonly Func _condition; + private readonly Func _iterate; + private readonly Func _resultSelector; + private readonly Func _timeSelector; + + public _(Relative parent, IObserver observer) + : base(observer) + { + _condition = parent._condition; + _iterate = parent._iterate; + _resultSelector = parent._resultSelector; + _timeSelector = parent._timeSelector; + + _first = true; + } + + private bool _first; + private bool _hasResult; + private TResult? _result; + + private MultipleAssignmentDisposableValue _timerDisposable; + + public void Run(IScheduler outerScheduler, TState initialState) + { + var timer = new SingleAssignmentDisposable(); + _timerDisposable.Disposable = timer; + timer.Disposable = outerScheduler.Schedule((@this: this, initialState), static (scheduler, tuple) => tuple.@this.InvokeRec(scheduler, tuple.initialState)); + } + + protected override void Dispose(bool disposing) + { + _timerDisposable.Dispose(); + base.Dispose(disposing); + } + + private IDisposable InvokeRec(IScheduler self, TState state) + { + if (_hasResult) + { + ForwardOnNext(_result!); + } + + var time = default(TimeSpan); + + try + { + if (_first) + { + _first = false; + } + else + { + state = _iterate(state); + } + + _hasResult = _condition(state); + + if (_hasResult) + { + _result = _resultSelector(state); + time = _timeSelector(state); + } + } + catch (Exception exception) + { + ForwardOnError(exception); + return Disposable.Empty; + } + + if (!_hasResult) + { + ForwardOnCompleted(); + return Disposable.Empty; + } + + var timer = new SingleAssignmentDisposable(); + _timerDisposable.Disposable = timer; + timer.Disposable = self.Schedule((@this: this, state), time, static (scheduler, tuple) => tuple.@this.InvokeRec(scheduler, tuple.state)); + + return Disposable.Empty; + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/GetEnumerator.cs b/LibExternal/System.Reactive/Linq/Observable/GetEnumerator.cs new file mode 100644 index 0000000..d586bd4 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/GetEnumerator.cs @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reactive.Disposables; +using System.Threading; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class GetEnumerator : IEnumerator, IObserver + { + private readonly ConcurrentQueue _queue; + private TSource? _current; + private Exception? _error; + private bool _done; + private bool _disposed; + private SingleAssignmentDisposableValue _subscription; + + private readonly SemaphoreSlim _gate; + + public GetEnumerator() + { + _queue = new ConcurrentQueue(); + _gate = new SemaphoreSlim(0); + } + + public IEnumerator Run(IObservable source) + { + // + // [OK] Use of unsafe Subscribe: non-pretentious exact mirror with the dual GetEnumerator method. + // + _subscription.Disposable = source.Subscribe/*Unsafe*/(this); + return this; + } + + public void OnNext(TSource value) + { + _queue.Enqueue(value); + _gate.Release(); + } + + public void OnError(Exception error) + { + _error = error; + _subscription.Dispose(); + _gate.Release(); + } + + public void OnCompleted() + { + _done = true; + _subscription.Dispose(); + _gate.Release(); + } + + public bool MoveNext() + { + _gate.Wait(); + + if (_disposed) + { + throw new ObjectDisposedException(""); + } + + if (_queue.TryDequeue(out _current)) + { + return true; + } + + _error?.Throw(); + + Debug.Assert(_done); + + _gate.Release(); // In the (rare) case the user calls MoveNext again we shouldn't block! + return false; + } + + public TSource Current => _current!; // NB: Only called after MoveNext returns true and assigns a value. + + object Collections.IEnumerator.Current => _current!; + + public void Dispose() + { + _subscription.Dispose(); + + _disposed = true; + _gate.Release(); + } + + public void Reset() + { + throw new NotSupportedException(); + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/GroupBy.cs b/LibExternal/System.Reactive/Linq/Observable/GroupBy.cs new file mode 100644 index 0000000..13ff559 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/GroupBy.cs @@ -0,0 +1,177 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Disposables; +using System.Reactive.Subjects; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class GroupBy : Producer, GroupBy._> + { + private readonly IObservable _source; + private readonly Func _keySelector; + private readonly Func _elementSelector; + private readonly int? _capacity; + private readonly IEqualityComparer _comparer; + + public GroupBy(IObservable source, Func keySelector, Func elementSelector, int? capacity, IEqualityComparer comparer) + { + _source = source; + _keySelector = keySelector; + _elementSelector = elementSelector; + _capacity = capacity; + _comparer = comparer; + } + + protected override _ CreateSink(IObserver> observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink> + { + private readonly Func _keySelector; + private readonly Func _elementSelector; + private readonly Grouping _map; + + private RefCountDisposable? _refCountDisposable; + private Subject? _null; + + public _(GroupBy parent, IObserver> observer) + : base(observer) + { + _keySelector = parent._keySelector; + _elementSelector = parent._elementSelector; + + if (parent._capacity.HasValue) + { + _map = new Grouping(parent._capacity.Value, parent._comparer); + } + else + { + _map = new Grouping(parent._comparer); + } + } + + public override void Run(IObservable source) + { + var sourceSubscription = new SingleAssignmentDisposable(); + _refCountDisposable = new RefCountDisposable(sourceSubscription); + sourceSubscription.Disposable = source.SubscribeSafe(this); + + SetUpstream(_refCountDisposable); + } + + public override void OnNext(TSource value) + { + TKey key; + try + { + key = _keySelector(value); + } + catch (Exception exception) + { + Error(exception); + return; + } + + var fireNewMapEntry = false; + Subject? writer; + try + { + // + // Note: The box instruction in the IL will be erased by the JIT in case T is + // a value type. In fact, the whole if block will go away and we'll end + // up with nothing but the TryGetValue check below. + // + // // var fireNewMapEntry = false; + // C:\Projects\Rx\Rx\Experimental\Main\Source\Rx\System.Reactive.Linq\Reactive\Linq\Observable\GroupBy.cs @ 67: + // 000007fb`6d544b80 48c7452800000000 mov qword ptr [rbp+28h],0 + // + // // var writer = default(ISubject); + // C:\Projects\Rx\Rx\Experimental\Main\Source\Rx\System.Reactive.Linq\Reactive\Linq\Observable\GroupBy.cs @ 66: + // 000007fb`6d544b88 c6453400 mov byte ptr [rbp+34h],0 + // + // // if (!_map.TryGetValue(key, out writer)) + // C:\Projects\Rx\Rx\Experimental\Main\Source\Rx\System.Reactive.Linq\Reactive\Linq\Observable\GroupBy.cs @ 86: + // 000007fb`6d544b8c 488b4560 mov rax,qword ptr [rbp+60h] + // ... + // + if (key == null) + { + if (_null == null) + { + _null = new Subject(); + fireNewMapEntry = true; + } + + writer = _null; + } + else + { + if (!_map.TryGetValue(key, out writer)) + { + writer = new Subject(); + _map.Add(key, writer); + fireNewMapEntry = true; + } + } + } + catch (Exception exception) + { + Error(exception); + return; + } + + if (fireNewMapEntry) + { + var group = new GroupedObservable(key, writer, _refCountDisposable!); // NB: _refCountDisposable is set in Run. + ForwardOnNext(group); + } + + TElement element; + try + { + element = _elementSelector(value); + } + catch (Exception exception) + { + Error(exception); + return; + } + + writer.OnNext(element); + } + + public override void OnError(Exception error) + { + Error(error); + } + + public override void OnCompleted() + { + _null?.OnCompleted(); + + foreach (var w in _map.Values) + { + w.OnCompleted(); + } + + ForwardOnCompleted(); + } + + private void Error(Exception exception) + { + _null?.OnError(exception); + + foreach (var w in _map.Values) + { + w.OnError(exception); + } + + ForwardOnError(exception); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/GroupByUntil.cs b/LibExternal/System.Reactive/Linq/Observable/GroupByUntil.cs new file mode 100644 index 0000000..0d5521f --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/GroupByUntil.cs @@ -0,0 +1,291 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Disposables; +using System.Reactive.Subjects; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class GroupByUntil : Producer, GroupByUntil._> + { + private readonly IObservable _source; + private readonly Func _keySelector; + private readonly Func _elementSelector; + private readonly Func, IObservable> _durationSelector; + private readonly int? _capacity; + private readonly IEqualityComparer _comparer; + + public GroupByUntil(IObservable source, Func keySelector, Func elementSelector, Func, IObservable> durationSelector, int? capacity, IEqualityComparer comparer) + { + _source = source; + _keySelector = keySelector; + _elementSelector = elementSelector; + _durationSelector = durationSelector; + _capacity = capacity; + _comparer = comparer; + } + + protected override _ CreateSink(IObserver> observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink> + { + private readonly object _gate = new(); + private readonly object _nullGate = new(); + private readonly CompositeDisposable _groupDisposable = new(); + private readonly RefCountDisposable _refCountDisposable; + private readonly Map> _map; + + private readonly Func _keySelector; + private readonly Func _elementSelector; + private readonly Func, IObservable> _durationSelector; + + private ISubject? _null; + + public _(GroupByUntil parent, IObserver> observer) + : base(observer) + { + _refCountDisposable = new RefCountDisposable(_groupDisposable); + _map = new Map>(parent._capacity, parent._comparer); + + _keySelector = parent._keySelector; + _elementSelector = parent._elementSelector; + _durationSelector = parent._durationSelector; + } + + public override void Run(IObservable source) + { + _groupDisposable.Add(source.SubscribeSafe(this)); + + SetUpstream(_refCountDisposable); + } + + private ISubject NewSubject() + { + var sub = new Subject(); + + return Subject.Create(new AsyncLockObserver(sub, new Concurrency.AsyncLock()), sub); + } + + public override void OnNext(TSource value) + { + TKey key; + try + { + key = _keySelector(value); + } + catch (Exception exception) + { + Error(exception); + return; + } + + var fireNewMapEntry = false; + ISubject writer; + try + { + // + // Note: The box instruction in the IL will be erased by the JIT in case T is + // a value type. In fact, the whole if block will go away and we'll end + // up with nothing but the GetOrAdd call below. + // + // See GroupBy for more information and confirmation of this fact using + // the SOS debugger extension. + // + if (key == null) + { + lock (_nullGate) + { + if (_null == null) + { + _null = NewSubject(); + fireNewMapEntry = true; + } + + writer = _null; + } + } + else + { + writer = _map.GetOrAdd(key, NewSubject, out fireNewMapEntry); + } + } + catch (Exception exception) + { + Error(exception); + return; + } + + if (fireNewMapEntry) + { + var group = new GroupedObservable(key, writer, _refCountDisposable); + + var durationGroup = new GroupedObservable(key, writer); + + IObservable duration; + try + { + duration = _durationSelector(durationGroup); + } + catch (Exception exception) + { + Error(exception); + return; + } + + lock (_gate) + { + ForwardOnNext(group); + } + + var durationObserver = new DurationObserver(this, key, writer); + _groupDisposable.Add(durationObserver); + durationObserver.SetResource(duration.SubscribeSafe(durationObserver)); + } + + TElement element; + try + { + element = _elementSelector(value); + } + catch (Exception exception) + { + Error(exception); + return; + } + + // + // ISSUE: Rx v1.x shipped without proper handling of the case where the duration + // sequence fires concurrently with the OnNext code path here. In such a + // case, the subject can be completed before we get a chance to send out + // a new element. However, a resurrected group for the same key won't get + // to see the element either. To guard against this case, we'd have to + // check whether the OnNext call below lost the race, and resurrect a new + // group if needed. Unfortunately, this complicates matters when the + // duration selector triggers synchronously (e.g. Return or Empty), which + // causes the group to terminate immediately. We should not get stuck in + // this case, repeatedly trying to resurrect a group that always ends + // before we can send the element into it. Also, users may expect this + // base case to mean no elements will ever be produced, so sending the + // element into the group before starting the duration sequence may not + // be a good idea either. For the time being, we'll leave this as-is and + // revisit the behavior for vNext. Nonetheless, we'll add synchronization + // to ensure no concurrent calls to the subject are made. + // + writer.OnNext(element); + } + + private sealed class DurationObserver : SafeObserver + { + private readonly _ _parent; + private readonly TKey _key; + private readonly ISubject _writer; + + public DurationObserver(_ parent, TKey key, ISubject writer) + { + _parent = parent; + _key = key; + _writer = writer; + } + + public override void OnNext(TDuration value) + { + OnCompleted(); + } + + public override void OnError(Exception error) + { + _parent.Error(error); + Dispose(); + } + + public override void OnCompleted() + { + if (_key == null) + { + ISubject? @null; + + lock (_parent._nullGate) + { + @null = _parent._null; + _parent._null = null; + } + + @null?.OnCompleted(); + } + else + { + if (_parent._map.Remove(_key)) + { + _writer.OnCompleted(); + } + } + + _parent._groupDisposable.Remove(this); + } + } + + public override void OnError(Exception error) + { + Error(error); + } + + public override void OnCompleted() + { + // + // NOTE: A race with OnCompleted triggered by a duration selector is fine when + // using Subject. It will transition into a terminal state, making one + // of the two calls a no-op by swapping in a DoneObserver. + // + ISubject? @null; + + lock (_nullGate) + { + @null = _null; + } + + @null?.OnCompleted(); + + foreach (var w in _map.Values) + { + w.OnCompleted(); + } + + lock (_gate) + { + ForwardOnCompleted(); + } + } + + private void Error(Exception exception) + { + // + // NOTE: A race with OnCompleted triggered by a duration selector is fine when + // using Subject. It will transition into a terminal state, making one + // of the two calls a no-op by swapping in a DoneObserver. + // + ISubject? @null; + + lock (_nullGate) + { + @null = _null; + } + + @null?.OnError(exception); + + foreach (var w in _map.Values) + { + w.OnError(exception); + } + + lock (_gate) + { + ForwardOnError(exception); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/GroupJoin.cs b/LibExternal/System.Reactive/Linq/Observable/GroupJoin.cs new file mode 100644 index 0000000..842dc54 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/GroupJoin.cs @@ -0,0 +1,312 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Disposables; +using System.Reactive.Subjects; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class GroupJoin : Producer._> + { + private readonly IObservable _left; + private readonly IObservable _right; + private readonly Func> _leftDurationSelector; + private readonly Func> _rightDurationSelector; + private readonly Func, TResult> _resultSelector; + + public GroupJoin(IObservable left, IObservable right, Func> leftDurationSelector, Func> rightDurationSelector, Func, TResult> resultSelector) + { + _left = left; + _right = right; + _leftDurationSelector = leftDurationSelector; + _rightDurationSelector = rightDurationSelector; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : IdentitySink + { + private readonly object _gate = new(); + private readonly CompositeDisposable _group = new(); + private readonly RefCountDisposable _refCount; + private readonly SortedDictionary> _leftMap; + private readonly SortedDictionary _rightMap; + + private readonly Func> _leftDurationSelector; + private readonly Func> _rightDurationSelector; + private readonly Func, TResult> _resultSelector; + + public _(GroupJoin parent, IObserver observer) + : base(observer) + { + _refCount = new RefCountDisposable(_group); + _leftMap = new SortedDictionary>(); + _rightMap = new SortedDictionary(); + + _leftDurationSelector = parent._leftDurationSelector; + _rightDurationSelector = parent._rightDurationSelector; + _resultSelector = parent._resultSelector; + } + + private int _leftID; + private int _rightID; + + public void Run(GroupJoin parent) + { + var leftObserver = new LeftObserver(this); + _group.Add(leftObserver); + + var rightObserver = new RightObserver(this); + _group.Add(rightObserver); + + leftObserver.SetResource(parent._left.SubscribeSafe(leftObserver)); + rightObserver.SetResource(parent._right.SubscribeSafe(rightObserver)); + + SetUpstream(_refCount); + } + + private sealed class LeftObserver : SafeObserver + { + private readonly _ _parent; + + public LeftObserver(_ parent) + { + _parent = parent; + } + + private void Expire(int id, IObserver group, IDisposable resource) + { + lock (_parent._gate) + { + if (_parent._leftMap.Remove(id)) + { + group.OnCompleted(); + } + } + + _parent._group.Remove(resource); + } + + public override void OnNext(TLeft value) + { + var s = new Subject(); + + int id; + int rightID; + + lock (_parent._gate) + { + id = _parent._leftID++; + rightID = _parent._rightID; + _parent._leftMap.Add(id, s); + } + + var window = new WindowObservable(s, _parent._refCount); + + // BREAKING CHANGE v2 > v1.x - Order of evaluation or the _leftDurationSelector and _resultSelector now consistent with Join. + + IObservable duration; + try + { + duration = _parent._leftDurationSelector(value); + } + catch (Exception exception) + { + OnError(exception); + return; + } + + var durationObserver = new DurationObserver(this, id, s); + _parent._group.Add(durationObserver); + // BREAKING CHANGE v2 > v1.x - The duration sequence is subscribed to before the result sequence is evaluated. + durationObserver.SetResource(duration.SubscribeSafe(durationObserver)); + + TResult result; + try + { + result = _parent._resultSelector(value, window); + } + catch (Exception exception) + { + OnError(exception); + return; + } + + lock (_parent._gate) + { + _parent.ForwardOnNext(result); + + foreach (var rightValue in _parent._rightMap) + { + if (rightValue.Key < rightID) + { + s.OnNext(rightValue.Value); + } + } + } + } + + private sealed class DurationObserver : SafeObserver + { + private readonly LeftObserver _parent; + private readonly int _id; + private readonly IObserver _group; + + public DurationObserver(LeftObserver parent, int id, IObserver group) + { + _parent = parent; + _id = id; + _group = group; + } + + public override void OnNext(TLeftDuration value) + { + _parent.Expire(_id, _group, this); + } + + public override void OnError(Exception error) + { + _parent.OnError(error); + } + + public override void OnCompleted() + { + _parent.Expire(_id, _group, this); + } + } + + public override void OnError(Exception error) + { + lock (_parent._gate) + { + foreach (var o in _parent._leftMap) + { + o.Value.OnError(error); + } + + _parent.ForwardOnError(error); + } + } + + public override void OnCompleted() + { + lock (_parent._gate) + { + _parent.ForwardOnCompleted(); + } + + Dispose(); + } + } + + private sealed class RightObserver : SafeObserver + { + private readonly _ _parent; + + public RightObserver(_ parent) + { + _parent = parent; + } + + private void Expire(int id, IDisposable resource) + { + lock (_parent._gate) + { + _parent._rightMap.Remove(id); + } + + _parent._group.Remove(resource); + } + + public override void OnNext(TRight value) + { + int id; + int leftID; + + lock (_parent._gate) + { + id = _parent._rightID++; + leftID = _parent._leftID; + _parent._rightMap.Add(id, value); + } + + IObservable duration; + try + { + duration = _parent._rightDurationSelector(value); + } + catch (Exception exception) + { + OnError(exception); + return; + } + + var durationObserver = new DurationObserver(this, id); + _parent._group.Add(durationObserver); + durationObserver.SetResource(duration.SubscribeSafe(durationObserver)); + + lock (_parent._gate) + { + foreach (var o in _parent._leftMap) + { + if (o.Key < leftID) + { + o.Value.OnNext(value); + } + } + } + } + + private sealed class DurationObserver : SafeObserver + { + private readonly RightObserver _parent; + private readonly int _id; + + public DurationObserver(RightObserver parent, int id) + { + _parent = parent; + _id = id; + } + + public override void OnNext(TRightDuration value) + { + _parent.Expire(_id, this); + } + + public override void OnError(Exception error) + { + _parent.OnError(error); + } + + public override void OnCompleted() + { + _parent.Expire(_id, this); + } + } + + public override void OnError(Exception error) + { + lock (_parent._gate) + { + foreach (var o in _parent._leftMap) + { + o.Value.OnError(error); + } + + _parent.ForwardOnError(error); + } + } + + public override void OnCompleted() + { + Dispose(); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/If.cs b/LibExternal/System.Reactive/Linq/Observable/If.cs new file mode 100644 index 0000000..e1d2f53 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/If.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class If : Producer._>, IEvaluatableObservable + { + private readonly Func _condition; + private readonly IObservable _thenSource; + private readonly IObservable _elseSource; + + public If(Func condition, IObservable thenSource, IObservable elseSource) + { + _condition = condition; + _thenSource = thenSource; + _elseSource = elseSource; + } + + public IObservable Eval() => _condition() ? _thenSource : _elseSource; + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(); + + internal sealed class _ : IdentitySink + { + private readonly If _parent; + + public _(If parent, IObserver observer) + : base(observer) + { + _parent = parent; + } + + public void Run() + { + IObservable result; + try + { + result = _parent.Eval(); + } + catch (Exception exception) + { + ForwardOnError(exception); + + return; + } + + SetUpstream(result.SubscribeSafe(this)); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/IgnoreElements.cs b/LibExternal/System.Reactive/Linq/Observable/IgnoreElements.cs new file mode 100644 index 0000000..3cca3fb --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/IgnoreElements.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class IgnoreElements : Producer._> + { + private readonly IObservable _source; + + public IgnoreElements(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(TSource value) + { + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/IsEmpty.cs b/LibExternal/System.Reactive/Linq/Observable/IsEmpty.cs new file mode 100644 index 0000000..2700ec6 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/IsEmpty.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class IsEmpty : Producer._> + { + private readonly IObservable _source; + + public IsEmpty(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(TSource value) + { + ForwardOnNext(false); + ForwardOnCompleted(); + } + + public override void OnCompleted() + { + ForwardOnNext(true); + ForwardOnCompleted(); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Join.cs b/LibExternal/System.Reactive/Linq/Observable/Join.cs new file mode 100644 index 0000000..3b7db67 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Join.cs @@ -0,0 +1,321 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Join : Producer._> + { + private readonly IObservable _left; + private readonly IObservable _right; + private readonly Func> _leftDurationSelector; + private readonly Func> _rightDurationSelector; + private readonly Func _resultSelector; + + public Join(IObservable left, IObservable right, Func> leftDurationSelector, Func> rightDurationSelector, Func resultSelector) + { + _left = left; + _right = right; + _leftDurationSelector = leftDurationSelector; + _rightDurationSelector = rightDurationSelector; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : IdentitySink + { + private readonly object _gate = new(); + private readonly CompositeDisposable _group = new(); + private readonly SortedDictionary _leftMap = new(); + private readonly SortedDictionary _rightMap = new(); + + private readonly Func> _leftDurationSelector; + private readonly Func> _rightDurationSelector; + private readonly Func _resultSelector; + + public _(Join parent, IObserver observer) + : base(observer) + { + _leftDurationSelector = parent._leftDurationSelector; + _rightDurationSelector = parent._rightDurationSelector; + _resultSelector = parent._resultSelector; + } + + private bool _leftDone; + private int _leftID; + + private bool _rightDone; + private int _rightID; + + public void Run(Join parent) + { + var leftObserver = new LeftObserver(this); + var rightObserver = new RightObserver(this); + + _group.Add(leftObserver); + _group.Add(rightObserver); + + leftObserver.SetResource(parent._left.SubscribeSafe(leftObserver)); + rightObserver.SetResource(parent._right.SubscribeSafe(rightObserver)); + + SetUpstream(_group); + } + + private sealed class LeftObserver : SafeObserver + { + private readonly _ _parent; + + public LeftObserver(_ parent) + { + _parent = parent; + } + + private void Expire(int id, IDisposable resource) + { + lock (_parent._gate) + { + if (_parent._leftMap.Remove(id) && _parent._leftMap.Count == 0 && _parent._leftDone) + { + _parent.ForwardOnCompleted(); + } + } + + _parent._group.Remove(resource); + } + + public override void OnNext(TLeft value) + { + int id; + int rightID; + + lock (_parent._gate) + { + id = _parent._leftID++; + rightID = _parent._rightID; + _parent._leftMap.Add(id, value); + } + + + IObservable duration; + try + { + duration = _parent._leftDurationSelector(value); + } + catch (Exception exception) + { + _parent.ForwardOnError(exception); + return; + } + + var durationObserver = new DurationObserver(this, id); + _parent._group.Add(durationObserver); + + durationObserver.SetResource(duration.SubscribeSafe(durationObserver)); + + lock (_parent._gate) + { + foreach (var rightValue in _parent._rightMap) + { + if (rightValue.Key < rightID) + { + TResult result; + try + { + result = _parent._resultSelector(value, rightValue.Value); + } + catch (Exception exception) + { + _parent.ForwardOnError(exception); + return; + } + + _parent.ForwardOnNext(result); + } + } + } + } + + private sealed class DurationObserver : SafeObserver + { + private readonly LeftObserver _parent; + private readonly int _id; + + public DurationObserver(LeftObserver parent, int id) + { + _parent = parent; + _id = id; + } + + public override void OnNext(TLeftDuration value) + { + _parent.Expire(_id, this); + } + + public override void OnError(Exception error) + { + _parent.OnError(error); + } + + public override void OnCompleted() + { + _parent.Expire(_id, this); + } + } + + public override void OnError(Exception error) + { + lock (_parent._gate) + { + _parent.ForwardOnError(error); + } + } + + public override void OnCompleted() + { + lock (_parent._gate) + { + _parent._leftDone = true; + if (_parent._rightDone || _parent._leftMap.Count == 0) + { + _parent.ForwardOnCompleted(); + } + else + { + Dispose(); + } + } + } + } + + private sealed class RightObserver : SafeObserver + { + private readonly _ _parent; + + public RightObserver(_ parent) + { + _parent = parent; + } + + private void Expire(int id, IDisposable resource) + { + lock (_parent._gate) + { + if (_parent._rightMap.Remove(id) && _parent._rightMap.Count == 0 && _parent._rightDone) + { + _parent.ForwardOnCompleted(); + } + } + + _parent._group.Remove(resource); + } + + public override void OnNext(TRight value) + { + int id; + int leftID; + + lock (_parent._gate) + { + id = _parent._rightID++; + leftID = _parent._leftID; + _parent._rightMap.Add(id, value); + } + + IObservable duration; + try + { + duration = _parent._rightDurationSelector(value); + } + catch (Exception exception) + { + _parent.ForwardOnError(exception); + return; + } + + var durationObserver = new DurationObserver(this, id); + _parent._group.Add(durationObserver); + durationObserver.SetResource(duration.SubscribeSafe(durationObserver)); + + lock (_parent._gate) + { + foreach (var leftValue in _parent._leftMap) + { + if (leftValue.Key < leftID) + { + TResult result; + try + { + result = _parent._resultSelector(leftValue.Value, value); + } + catch (Exception exception) + { + _parent.ForwardOnError(exception); + return; + } + + _parent.ForwardOnNext(result); + } + } + } + } + + private sealed class DurationObserver : SafeObserver + { + private readonly RightObserver _parent; + private readonly int _id; + + public DurationObserver(RightObserver parent, int id) + { + _parent = parent; + _id = id; + } + + public override void OnNext(TRightDuration value) + { + _parent.Expire(_id, this); + } + + public override void OnError(Exception error) + { + _parent.OnError(error); + } + + public override void OnCompleted() + { + _parent.Expire(_id, this); + } + } + + public override void OnError(Exception error) + { + lock (_parent._gate) + { + _parent.ForwardOnError(error); + } + } + + public override void OnCompleted() + { + lock (_parent._gate) + { + _parent._rightDone = true; + if (_parent._leftDone || _parent._rightMap.Count == 0) + { + _parent.ForwardOnCompleted(); + } + else + { + Dispose(); + } + } + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/LastAsync.cs b/LibExternal/System.Reactive/Linq/Observable/LastAsync.cs new file mode 100644 index 0000000..890c388 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/LastAsync.cs @@ -0,0 +1,148 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class LastAsync + { + internal sealed class Sequence : Producer + { + private readonly IObservable _source; + + public Sequence(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private TSource? _value; + private bool _seenValue; + + public _(IObserver observer) + : base(observer) + { + + } + + public override void OnNext(TSource value) + { + _value = value; + _seenValue = true; + } + + public override void OnError(Exception error) + { + _value = default; + ForwardOnError(error); + } + + public override void OnCompleted() + { + if (!_seenValue) + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + else + { + var value = _value!; + _value = default; + ForwardOnNext(value); + ForwardOnCompleted(); + } + } + } + } + + internal sealed class Predicate : Producer + { + private readonly IObservable _source; + private readonly Func _predicate; + + public Predicate(IObservable source, Func predicate) + { + _source = source; + _predicate = predicate; + } + + protected override _ CreateSink(IObserver observer) => new(_predicate, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly Func _predicate; + private TSource? _value; + private bool _seenValue; + + public _(Func predicate, IObserver observer) + : base(observer) + { + _predicate = predicate; + } + + public override void OnNext(TSource value) + { + var b = false; + + try + { + b = _predicate(value); + } + catch (Exception ex) + { + _value = default; + ForwardOnError(ex); + return; + } + + if (b) + { + _value = value; + _seenValue = true; + } + } + + public override void OnError(Exception error) + { + _value = default; + ForwardOnError(error); + } + + public override void OnCompleted() + { + if (!_seenValue) + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_MATCHING_ELEMENTS); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + else + { + var value = _value!; + _value = default; + ForwardOnNext(value); + ForwardOnCompleted(); + } + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/LastOrDefaultAsync.cs b/LibExternal/System.Reactive/Linq/Observable/LastOrDefaultAsync.cs new file mode 100644 index 0000000..c6a6622 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/LastOrDefaultAsync.cs @@ -0,0 +1,116 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class LastOrDefaultAsync + { + internal sealed class Sequence : Producer + { + private readonly IObservable _source; + + public Sequence(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private TSource? _value; + + public _(IObserver observer) + : base(observer) + { + + } + + public override void OnNext(TSource value) + { + _value = value; + } + + public override void OnError(Exception error) + { + _value = default; + ForwardOnError(error); + } + + public override void OnCompleted() + { + var value = _value; + _value = default; + ForwardOnNext(value); + ForwardOnCompleted(); + } + } + } + + internal sealed class Predicate : Producer + { + private readonly IObservable _source; + private readonly Func _predicate; + + public Predicate(IObservable source, Func predicate) + { + _source = source; + _predicate = predicate; + } + + protected override _ CreateSink(IObserver observer) => new(_predicate, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private readonly Func _predicate; + private TSource? _value; + + public _(Func predicate, IObserver observer) + : base(observer) + { + _predicate = predicate; + } + + public override void OnNext(TSource value) + { + var b = false; + + try + { + b = _predicate(value); + } + catch (Exception ex) + { + _value = default; + ForwardOnError(ex); + return; + } + + if (b) + { + _value = value; + } + } + + public override void OnError(Exception error) + { + _value = default; + ForwardOnError(error); + } + + public override void OnCompleted() + { + var value = _value; + _value = default; + ForwardOnNext(value); + ForwardOnCompleted(); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Latest.cs b/LibExternal/System.Reactive/Linq/Observable/Latest.cs new file mode 100644 index 0000000..0d81b20 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Latest.cs @@ -0,0 +1,135 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics.CodeAnalysis; +using System.Threading; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Latest : PushToPullAdapter + { + public Latest(IObservable source) + : base(source) + { + } + + protected override PushToPullSink Run() + { + return new _(); + } + + private sealed class _ : PushToPullSink + { + private readonly object _gate; + private readonly SemaphoreSlim _semaphore; + + public _() + { + _gate = new object(); + _semaphore = new SemaphoreSlim(0, 1); + } + + private bool _notificationAvailable; + private NotificationKind _kind; + private TSource? _value; + private Exception? _error; + + public override void OnNext(TSource value) + { + var lackedValue = false; + lock (_gate) + { + lackedValue = !_notificationAvailable; + _notificationAvailable = true; + _kind = NotificationKind.OnNext; + _value = value; + } + + if (lackedValue) + { + _semaphore.Release(); + } + } + + public override void OnError(Exception error) + { + Dispose(); + + var lackedValue = false; + lock (_gate) + { + lackedValue = !_notificationAvailable; + _notificationAvailable = true; + _kind = NotificationKind.OnError; + _error = error; + } + + if (lackedValue) + { + _semaphore.Release(); + } + } + + public override void OnCompleted() + { + Dispose(); + + var lackedValue = false; + lock (_gate) + { + lackedValue = !_notificationAvailable; + _notificationAvailable = true; + _kind = NotificationKind.OnCompleted; + } + + if (lackedValue) + { + _semaphore.Release(); + } + } + + public override bool TryMoveNext([MaybeNullWhen(false)] out TSource current) + { + NotificationKind kind; + + var value = default(TSource); + var error = default(Exception); + + _semaphore.Wait(); + + lock (_gate) + { + kind = _kind; + + switch (kind) + { + case NotificationKind.OnNext: + value = _value; + break; + case NotificationKind.OnError: + error = _error; + break; + } + + _notificationAvailable = false; + } + + switch (kind) + { + case NotificationKind.OnNext: + current = value!; + return true; + case NotificationKind.OnError: + error!.Throw(); + break; + case NotificationKind.OnCompleted: + break; + } + + current = default; + return false; + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/LongCount.cs b/LibExternal/System.Reactive/Linq/Observable/LongCount.cs new file mode 100644 index 0000000..d04f3ff --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/LongCount.cs @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class LongCount + { + internal sealed class All : Producer + { + private readonly IObservable _source; + + public All(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private long _count; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(TSource value) + { + try + { + checked + { + _count++; + } + } + catch (Exception ex) + { + ForwardOnError(ex); + } + } + + public override void OnCompleted() + { + ForwardOnNext(_count); + ForwardOnCompleted(); + } + } + } + + internal sealed class Predicate : Producer + { + private readonly IObservable _source; + private readonly Func _predicate; + + public Predicate(IObservable source, Func predicate) + { + _source = source; + _predicate = predicate; + } + + protected override _ CreateSink(IObserver observer) => new(_predicate, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private readonly Func _predicate; + private long _count; + + public _(Func predicate, IObserver observer) + : base(observer) + { + _predicate = predicate; + } + + public override void OnNext(TSource value) + { + try + { + checked + { + if (_predicate(value)) + { + _count++; + } + } + } + catch (Exception ex) + { + ForwardOnError(ex); + } + } + + public override void OnCompleted() + { + ForwardOnNext(_count); + ForwardOnCompleted(); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Materialize.cs b/LibExternal/System.Reactive/Linq/Observable/Materialize.cs new file mode 100644 index 0000000..0f833c0 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Materialize.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Materialize : Producer, Materialize._> + { + private readonly IObservable _source; + + public Materialize(IObservable source) + { + _source = source; + } + + public IObservable Dematerialize() => _source.AsObservable(); + + protected override _ CreateSink(IObserver> observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink> + { + public _(IObserver> observer) + : base(observer) + { + } + + public override void OnNext(TSource value) + { + ForwardOnNext(Notification.CreateOnNext(value)); + } + + public override void OnError(Exception error) + { + ForwardOnNext(Notification.CreateOnError(error)); + ForwardOnCompleted(); + } + + public override void OnCompleted() + { + ForwardOnNext(Notification.CreateOnCompleted()); + ForwardOnCompleted(); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Max.cs b/LibExternal/System.Reactive/Linq/Observable/Max.cs new file mode 100644 index 0000000..18936fb --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Max.cs @@ -0,0 +1,699 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Max : Producer._> + { + private readonly IObservable _source; + private readonly IComparer _comparer; + + public Max(IObservable source, IComparer comparer) + { + _source = source; + _comparer = comparer; + } + + protected override _ CreateSink(IObserver observer) => default(TSource) == null ? (_)new Null(_comparer, observer) : new NonNull(_comparer, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal abstract class _ : IdentitySink + { + protected readonly IComparer _comparer; + + protected _(IComparer comparer, IObserver observer) + : base(observer) + { + _comparer = comparer; + } + } + + private sealed class NonNull : _ + { + private bool _hasValue; + private TSource? _lastValue; + + public NonNull(IComparer comparer, IObserver observer) + : base(comparer, observer) + { + } + + public override void OnNext(TSource value) + { + if (_hasValue) + { + int comparison; + try + { + comparison = _comparer.Compare(value, _lastValue!); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + if (comparison > 0) + { + _lastValue = value; + } + } + else + { + _hasValue = true; + _lastValue = value; + } + } + + public override void OnCompleted() + { + if (!_hasValue) + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + else + { + ForwardOnNext(_lastValue!); + ForwardOnCompleted(); + } + } + } + + private sealed class Null : _ + { + private TSource? _lastValue; + + public Null(IComparer comparer, IObserver observer) + : base(comparer, observer) + { + } + + public override void OnNext(TSource value) + { + if (value != null) + { + if (_lastValue == null) + { + _lastValue = value; + } + else + { + int comparison; + try + { + comparison = _comparer.Compare(value, _lastValue); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + if (comparison > 0) + { + _lastValue = value; + } + } + } + } + + public override void OnError(Exception error) + { + ForwardOnError(error); + } + + public override void OnCompleted() + { + ForwardOnNext(_lastValue!); + ForwardOnCompleted(); + } + } + } + + internal sealed class MaxDouble : Producer + { + private readonly IObservable _source; + + public MaxDouble(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private bool _hasValue; + private double _lastValue; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(double value) + { + if (_hasValue) + { + if (value > _lastValue || double.IsNaN(value)) + { + _lastValue = value; + } + } + else + { + _lastValue = value; + _hasValue = true; + } + } + + public override void OnCompleted() + { + if (!_hasValue) + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + else + { + ForwardOnNext(_lastValue); + ForwardOnCompleted(); + } + } + } + } + + internal sealed class MaxSingle : Producer + { + private readonly IObservable _source; + + public MaxSingle(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private bool _hasValue; + private float _lastValue; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(float value) + { + if (_hasValue) + { + if (value > _lastValue || float.IsNaN(value)) + { + _lastValue = value; + } + } + else + { + _lastValue = value; + _hasValue = true; + } + } + + public override void OnCompleted() + { + if (!_hasValue) + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + else + { + ForwardOnNext(_lastValue); + ForwardOnCompleted(); + } + } + } + } + + internal sealed class MaxDecimal : Producer + { + private readonly IObservable _source; + + public MaxDecimal(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private bool _hasValue; + private decimal _lastValue; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(decimal value) + { + if (_hasValue) + { + if (value > _lastValue) + { + _lastValue = value; + } + } + else + { + _lastValue = value; + _hasValue = true; + } + } + + public override void OnCompleted() + { + if (!_hasValue) + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + else + { + ForwardOnNext(_lastValue); + ForwardOnCompleted(); + } + } + } + } + + internal sealed class MaxInt32 : Producer + { + private readonly IObservable _source; + + public MaxInt32(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private bool _hasValue; + private int _lastValue; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(int value) + { + if (_hasValue) + { + if (value > _lastValue) + { + _lastValue = value; + } + } + else + { + _lastValue = value; + _hasValue = true; + } + } + + public override void OnCompleted() + { + if (!_hasValue) + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + else + { + ForwardOnNext(_lastValue); + ForwardOnCompleted(); + } + } + } + } + + internal sealed class MaxInt64 : Producer + { + private readonly IObservable _source; + + public MaxInt64(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private bool _hasValue; + private long _lastValue; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(long value) + { + if (_hasValue) + { + if (value > _lastValue) + { + _lastValue = value; + } + } + else + { + _lastValue = value; + _hasValue = true; + } + } + + public override void OnCompleted() + { + if (!_hasValue) + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + else + { + ForwardOnNext(_lastValue); + ForwardOnCompleted(); + } + } + } + } + + internal sealed class MaxDoubleNullable : Producer + { + private readonly IObservable _source; + + public MaxDoubleNullable(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private double? _lastValue; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(double? value) + { + if (!value.HasValue) + { + return; + } + + if (_lastValue.HasValue) + { + if (value > _lastValue || double.IsNaN((double)value)) + { + _lastValue = value; + } + } + else + { + _lastValue = value; + } + } + + public override void OnCompleted() + { + ForwardOnNext(_lastValue); + ForwardOnCompleted(); + } + } + } + + internal sealed class MaxSingleNullable : Producer + { + private readonly IObservable _source; + + public MaxSingleNullable(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private float? _lastValue; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(float? value) + { + if (!value.HasValue) + { + return; + } + + if (_lastValue.HasValue) + { + if (value > _lastValue || float.IsNaN((float)value)) + { + _lastValue = value; + } + } + else + { + _lastValue = value; + } + } + + public override void OnCompleted() + { + ForwardOnNext(_lastValue); + ForwardOnCompleted(); + } + } + } + + internal sealed class MaxDecimalNullable : Producer + { + private readonly IObservable _source; + + public MaxDecimalNullable(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private decimal? _lastValue; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(decimal? value) + { + if (!value.HasValue) + { + return; + } + + if (_lastValue.HasValue) + { + if (value > _lastValue) + { + _lastValue = value; + } + } + else + { + _lastValue = value; + } + } + + public override void OnCompleted() + { + ForwardOnNext(_lastValue); + ForwardOnCompleted(); + } + } + } + + internal sealed class MaxInt32Nullable : Producer + { + private readonly IObservable _source; + + public MaxInt32Nullable(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private int? _lastValue; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(int? value) + { + if (!value.HasValue) + { + return; + } + + if (_lastValue.HasValue) + { + if (value > _lastValue) + { + _lastValue = value; + } + } + else + { + _lastValue = value; + } + } + + public override void OnCompleted() + { + ForwardOnNext(_lastValue); + ForwardOnCompleted(); + } + } + } + + internal sealed class MaxInt64Nullable : Producer + { + private readonly IObservable _source; + + public MaxInt64Nullable(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private long? _lastValue; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(long? value) + { + if (!value.HasValue) + { + return; + } + + if (_lastValue.HasValue) + { + if (value > _lastValue) + { + _lastValue = value; + } + } + else + { + _lastValue = value; + } + } + + public override void OnCompleted() + { + ForwardOnNext(_lastValue); + ForwardOnCompleted(); + } + } + } +} + diff --git a/LibExternal/System.Reactive/Linq/Observable/MaxBy.cs b/LibExternal/System.Reactive/Linq/Observable/MaxBy.cs new file mode 100644 index 0000000..f3f5689 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/MaxBy.cs @@ -0,0 +1,111 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class MaxBy : Producer, MaxBy._> + { + private readonly IObservable _source; + private readonly Func _keySelector; + private readonly IComparer _comparer; + + public MaxBy(IObservable source, Func keySelector, IComparer comparer) + { + _source = source; + _keySelector = keySelector; + _comparer = comparer; + } + + protected override _ CreateSink(IObserver> observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink> + { + private readonly Func _keySelector; + private readonly IComparer _comparer; + private bool _hasValue; + private TKey? _lastKey; + private List _list; + + public _(MaxBy parent, IObserver> observer) + : base(observer) + { + _keySelector = parent._keySelector; + _comparer = parent._comparer; + + _list = new List(); + } + + public override void OnNext(TSource value) + { + TKey key; + try + { + key = _keySelector(value); + } + catch (Exception ex) + { + Cleanup(); + ForwardOnError(ex); + return; + } + + var comparison = 0; + + if (!_hasValue) + { + _hasValue = true; + _lastKey = key; + } + else + { + try + { + comparison = _comparer.Compare(key, _lastKey!); + } + catch (Exception ex) + { + Cleanup(); + ForwardOnError(ex); + return; + } + } + + if (comparison > 0) + { + _lastKey = key; + _list.Clear(); + } + + if (comparison >= 0) + { + _list.Add(value); + } + } + + public override void OnError(Exception error) + { + Cleanup(); + base.OnError(error); + } + + public override void OnCompleted() + { + var list = _list; + Cleanup(); + ForwardOnNext(list); + ForwardOnCompleted(); + } + + private void Cleanup() + { + _list = null!; + _lastKey = default; + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Merge.cs b/LibExternal/System.Reactive/Linq/Observable/Merge.cs new file mode 100644 index 0000000..06e5971 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Merge.cs @@ -0,0 +1,372 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Concurrency; +using System.Reactive.Disposables; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class Merge + { + internal sealed class ObservablesMaxConcurrency : Producer + { + private readonly IObservable> _sources; + private readonly int _maxConcurrent; + + public ObservablesMaxConcurrency(IObservable> sources, int maxConcurrent) + { + _sources = sources; + _maxConcurrent = maxConcurrent; + } + + protected override _ CreateSink(IObserver observer) => new(_maxConcurrent, observer); + + protected override void Run(_ sink) => sink.Run(_sources); + + internal sealed class _ : Sink, TSource> + { + private readonly object _gate = new(); + private readonly int _maxConcurrent; + private readonly Queue> _q = new(); + private readonly CompositeDisposable _group = new(); + + public _(int maxConcurrent, IObserver observer) + : base(observer) + { + _maxConcurrent = maxConcurrent; + } + + private volatile bool _isStopped; + private int _activeCount; + + public override void OnNext(IObservable value) + { + lock (_gate) + { + if (_activeCount < _maxConcurrent) + { + _activeCount++; + Subscribe(value); + } + else + { + _q.Enqueue(value); + } + } + } + + public override void OnError(Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + lock (_gate) + { + _isStopped = true; + if (_activeCount == 0) + { + ForwardOnCompleted(); + } + else + { + DisposeUpstream(); + } + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _group.Dispose(); + } + } + + private void Subscribe(IObservable innerSource) + { + var innerObserver = new InnerObserver(this); + _group.Add(innerObserver); + innerObserver.SetResource(innerSource.SubscribeSafe(innerObserver)); + } + + private sealed class InnerObserver : SafeObserver + { + private readonly _ _parent; + + public InnerObserver(_ parent) + { + _parent = parent; + } + + public override void OnNext(TSource value) + { + lock (_parent._gate) + { + _parent.ForwardOnNext(value); + } + } + + public override void OnError(Exception error) + { + lock (_parent._gate) + { + _parent.ForwardOnError(error); + } + } + + public override void OnCompleted() + { + _parent._group.Remove(this); + lock (_parent._gate) + { + if (_parent._q.Count > 0) + { + var s = _parent._q.Dequeue(); + _parent.Subscribe(s); + } + else + { + _parent._activeCount--; + if (_parent._isStopped && _parent._activeCount == 0) + { + _parent.ForwardOnCompleted(); + } + } + } + } + } + } + } + + internal sealed class Observables : Producer + { + private readonly IObservable> _sources; + + public Observables(IObservable> sources) + { + _sources = sources; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_sources); + + internal sealed class _ : Sink, TSource> + { + private readonly object _gate = new(); + private readonly CompositeDisposable _group = new(); + + public _(IObserver observer) + : base(observer) + { + } + + private volatile bool _isStopped; + + public override void OnNext(IObservable value) + { + var innerObserver = new InnerObserver(this); + _group.Add(innerObserver); + innerObserver.SetResource(value.SubscribeSafe(innerObserver)); + } + + public override void OnError(Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + _isStopped = true; + if (_group.Count == 0) + { + // + // Notice there can be a race between OnCompleted of the source and any + // of the inner sequences, where both see _group.Count == 1, and one is + // waiting for the lock. There won't be a double OnCompleted observation + // though, because the call to Dispose silences the observer by swapping + // in a NopObserver. + // + lock (_gate) + { + ForwardOnCompleted(); + } + } + else + { + DisposeUpstream(); + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _group.Dispose(); + } + } + + private sealed class InnerObserver : SafeObserver + { + private readonly _ _parent; + + public InnerObserver(_ parent) + { + _parent = parent; + } + + public override void OnNext(TSource value) + { + lock (_parent._gate) + { + _parent.ForwardOnNext(value); + } + } + + public override void OnError(Exception error) + { + lock (_parent._gate) + { + _parent.ForwardOnError(error); + } + } + + public override void OnCompleted() + { + _parent._group.Remove(this); + if (_parent._isStopped && _parent._group.Count == 0) + { + // + // Notice there can be a race between OnCompleted of the source and any + // of the inner sequences, where both see _group.Count == 1, and one is + // waiting for the lock. There won't be a double OnCompleted observation + // though, because the call to Dispose silences the observer by swapping + // in a NopObserver. + // + lock (_parent._gate) + { + _parent.ForwardOnCompleted(); + } + } + } + } + } + } + + internal sealed class Tasks : Producer + { + private readonly IObservable> _sources; + + public Tasks(IObservable> sources) + { + _sources = sources; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_sources); + + internal sealed class _ : Sink, TSource> + { + private readonly object _gate = new(); + private readonly CancellationTokenSource _cts = new(); + + public _(IObserver observer) + : base(observer) + { + } + + private volatile int _count = 1; + + public override void OnNext(Task value) + { + Interlocked.Increment(ref _count); + if (value.IsCompleted) + { + OnCompletedTask(value); + } + else + { + value.ContinueWith((t, thisObject) => ((_)thisObject!).OnCompletedTask(t), this, _cts.Token); + } + } + + private void OnCompletedTask(Task task) + { + switch (task.Status) + { + case TaskStatus.RanToCompletion: + { + lock (_gate) + { + ForwardOnNext(task.Result); + } + + OnCompleted(); + } + break; + case TaskStatus.Faulted: + { + lock (_gate) + { + ForwardOnError(TaskHelpers.GetSingleException(task)); + } + } + break; + case TaskStatus.Canceled: + { + lock (_gate) + { + ForwardOnError(new TaskCanceledException(task)); + } + } + break; + } + } + + public override void OnError(Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + if (Interlocked.Decrement(ref _count) == 0) + { + lock (_gate) + { + ForwardOnCompleted(); + } + } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + _cts.Cancel(); + + base.Dispose(disposing); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Min.cs b/LibExternal/System.Reactive/Linq/Observable/Min.cs new file mode 100644 index 0000000..e278520 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Min.cs @@ -0,0 +1,698 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Min : Producer._> + { + private readonly IObservable _source; + private readonly IComparer _comparer; + + public Min(IObservable source, IComparer comparer) + { + _source = source; + _comparer = comparer; + } + + protected override _ CreateSink(IObserver observer) => default(TSource) == null ? (_)new Null(_comparer, observer) : new NonNull(_comparer, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal abstract class _ : IdentitySink + { + protected readonly IComparer _comparer; + + protected _(IComparer comparer, IObserver observer) + : base(observer) + { + _comparer = comparer; + } + } + + private sealed class NonNull : _ + { + private bool _hasValue; + private TSource? _lastValue; + + public NonNull(IComparer comparer, IObserver observer) + : base(comparer, observer) + { + } + + public override void OnNext(TSource value) + { + if (_hasValue) + { + int comparison; + try + { + comparison = _comparer.Compare(value, _lastValue!); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + if (comparison < 0) + { + _lastValue = value; + } + } + else + { + _hasValue = true; + _lastValue = value; + } + } + + public override void OnError(Exception error) + { + ForwardOnError(error); + } + + public override void OnCompleted() + { + if (!_hasValue) + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + else + { + ForwardOnNext(_lastValue!); + ForwardOnCompleted(); + } + } + } + + private sealed class Null : _ + { + private TSource? _lastValue; + + public Null(IComparer comparer, IObserver observer) + : base(comparer, observer) + { + } + + public override void OnNext(TSource value) + { + if (value != null) + { + if (_lastValue == null) + { + _lastValue = value; + } + else + { + int comparison; + try + { + comparison = _comparer.Compare(value, _lastValue); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + if (comparison < 0) + { + _lastValue = value; + } + } + } + } + + public override void OnCompleted() + { + ForwardOnNext(_lastValue!); + ForwardOnCompleted(); + } + } + } + + internal sealed class MinDouble : Producer + { + private readonly IObservable _source; + + public MinDouble(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private bool _hasValue; + private double _lastValue; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(double value) + { + if (_hasValue) + { + if (value < _lastValue || double.IsNaN(value)) + { + _lastValue = value; + } + } + else + { + _lastValue = value; + _hasValue = true; + } + } + + public override void OnCompleted() + { + if (!_hasValue) + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + else + { + ForwardOnNext(_lastValue); + ForwardOnCompleted(); + } + } + } + } + + internal sealed class MinSingle : Producer + { + private readonly IObservable _source; + + public MinSingle(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private bool _hasValue; + private float _lastValue; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(float value) + { + if (_hasValue) + { + if (value < _lastValue || float.IsNaN(value)) + { + _lastValue = value; + } + } + else + { + _lastValue = value; + _hasValue = true; + } + } + + public override void OnCompleted() + { + if (!_hasValue) + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + else + { + ForwardOnNext(_lastValue); + ForwardOnCompleted(); + } + } + } + } + + internal sealed class MinDecimal : Producer + { + private readonly IObservable _source; + + public MinDecimal(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private bool _hasValue; + private decimal _lastValue; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(decimal value) + { + if (_hasValue) + { + if (value < _lastValue) + { + _lastValue = value; + } + } + else + { + _lastValue = value; + _hasValue = true; + } + } + + public override void OnCompleted() + { + if (!_hasValue) + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + else + { + ForwardOnNext(_lastValue); + ForwardOnCompleted(); + } + } + } + } + + internal sealed class MinInt32 : Producer + { + private readonly IObservable _source; + + public MinInt32(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private bool _hasValue; + private int _lastValue; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(int value) + { + if (_hasValue) + { + if (value < _lastValue) + { + _lastValue = value; + } + } + else + { + _lastValue = value; + _hasValue = true; + } + } + + public override void OnCompleted() + { + if (!_hasValue) + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + else + { + ForwardOnNext(_lastValue); + ForwardOnCompleted(); + } + } + } + } + + internal sealed class MinInt64 : Producer + { + private readonly IObservable _source; + + public MinInt64(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private bool _hasValue; + private long _lastValue; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(long value) + { + if (_hasValue) + { + if (value < _lastValue) + { + _lastValue = value; + } + } + else + { + _lastValue = value; + _hasValue = true; + } + } + + public override void OnCompleted() + { + if (!_hasValue) + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + else + { + ForwardOnNext(_lastValue); + ForwardOnCompleted(); + } + } + } + } + + internal sealed class MinDoubleNullable : Producer + { + private readonly IObservable _source; + + public MinDoubleNullable(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private double? _lastValue; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(double? value) + { + if (!value.HasValue) + { + return; + } + + if (_lastValue.HasValue) + { + if (value < _lastValue || double.IsNaN((double)value)) + { + _lastValue = value; + } + } + else + { + _lastValue = value; + } + } + + public override void OnCompleted() + { + ForwardOnNext(_lastValue); + ForwardOnCompleted(); + } + } + } + + internal sealed class MinSingleNullable : Producer + { + private readonly IObservable _source; + + public MinSingleNullable(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private float? _lastValue; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(float? value) + { + if (!value.HasValue) + { + return; + } + + if (_lastValue.HasValue) + { + if (value < _lastValue || float.IsNaN((float)value)) + { + _lastValue = value; + } + } + else + { + _lastValue = value; + } + } + + public override void OnCompleted() + { + ForwardOnNext(_lastValue); + ForwardOnCompleted(); + } + } + } + + internal sealed class MinDecimalNullable : Producer + { + private readonly IObservable _source; + + public MinDecimalNullable(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private decimal? _lastValue; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(decimal? value) + { + if (!value.HasValue) + { + return; + } + + if (_lastValue.HasValue) + { + if (value < _lastValue) + { + _lastValue = value; + } + } + else + { + _lastValue = value; + } + } + + public override void OnCompleted() + { + ForwardOnNext(_lastValue); + ForwardOnCompleted(); + } + } + } + + internal sealed class MinInt32Nullable : Producer + { + private readonly IObservable _source; + + public MinInt32Nullable(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private int? _lastValue; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(int? value) + { + if (!value.HasValue) + { + return; + } + + if (_lastValue.HasValue) + { + if (value < _lastValue) + { + _lastValue = value; + } + } + else + { + _lastValue = value; + } + } + + public override void OnCompleted() + { + ForwardOnNext(_lastValue); + ForwardOnCompleted(); + } + } + } + + internal sealed class MinInt64Nullable : Producer + { + private readonly IObservable _source; + + public MinInt64Nullable(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private long? _lastValue; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(long? value) + { + if (!value.HasValue) + { + return; + } + + if (_lastValue.HasValue) + { + if (value < _lastValue) + { + _lastValue = value; + } + } + else + { + _lastValue = value; + } + } + + public override void OnCompleted() + { + ForwardOnNext(_lastValue); + ForwardOnCompleted(); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/MinBy.cs b/LibExternal/System.Reactive/Linq/Observable/MinBy.cs new file mode 100644 index 0000000..6c87ab8 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/MinBy.cs @@ -0,0 +1,105 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class MinBy : Producer, MinBy._> + { + private readonly IObservable _source; + private readonly Func _keySelector; + private readonly IComparer _comparer; + + public MinBy(IObservable source, Func keySelector, IComparer comparer) + { + _source = source; + _keySelector = keySelector; + _comparer = comparer; + } + + protected override _ CreateSink(IObserver> observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink> + { + private readonly Func _keySelector; + private readonly IComparer _comparer; + private bool _hasValue; + private TKey? _lastKey; + private List _list; + + public _(MinBy parent, IObserver> observer) + : base(observer) + { + _keySelector = parent._keySelector; + _comparer = parent._comparer; + + _list = new List(); + } + + public override void OnNext(TSource value) + { + TKey key; + try + { + key = _keySelector(value); + } + catch (Exception ex) + { + Cleanup(); + ForwardOnError(ex); + return; + } + + var comparison = 0; + + if (!_hasValue) + { + _hasValue = true; + _lastKey = key; + } + else + { + try + { + comparison = _comparer.Compare(key, _lastKey!); + } + catch (Exception ex) + { + Cleanup(); + ForwardOnError(ex); + return; + } + } + + if (comparison < 0) + { + _lastKey = key; + _list.Clear(); + } + + if (comparison <= 0) + { + _list.Add(value); + } + } + + public override void OnCompleted() + { + var list = _list; + Cleanup(); + ForwardOnNext(list); + ForwardOnCompleted(); + } + + private void Cleanup() + { + _list = null!; + _lastKey = default; + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/MostRecent.cs b/LibExternal/System.Reactive/Linq/Observable/MostRecent.cs new file mode 100644 index 0000000..6bf503c --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/MostRecent.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics.CodeAnalysis; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class MostRecent : PushToPullAdapter + { + private readonly TSource _initialValue; + + public MostRecent(IObservable source, TSource initialValue) + : base(source) + { + _initialValue = initialValue; + } + + protected override PushToPullSink Run() + { + return new _(_initialValue); + } + + private sealed class _ : PushToPullSink + { + public _(TSource initialValue) + { + _kind = NotificationKind.OnNext; + _value = initialValue; + } + + private volatile NotificationKind _kind; + private TSource _value; + private Exception? _error; + + public override void OnNext(TSource value) + { + _value = value; + _kind = NotificationKind.OnNext; // Write last! + } + + public override void OnError(Exception error) + { + Dispose(); + + _error = error; + _kind = NotificationKind.OnError; // Write last! + } + + public override void OnCompleted() + { + Dispose(); + + _kind = NotificationKind.OnCompleted; // Write last! + } + + public override bool TryMoveNext([MaybeNullWhen(false)]out TSource current) + { + // + // Notice the _kind field is marked volatile and read before the other fields. + // + // In case of a concurrent change, we may read a stale OnNext value, which is + // fine because this push-to-pull adapter is about sampling. + // + switch (_kind) + { + case NotificationKind.OnNext: + current = _value; + return true; + case NotificationKind.OnError: + _error!.Throw(); + break; + case NotificationKind.OnCompleted: + break; + } + + current = default; + return false; + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Multicast.cs b/LibExternal/System.Reactive/Linq/Observable/Multicast.cs new file mode 100644 index 0000000..26f5cc1 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Multicast.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; +using System.Reactive.Subjects; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Multicast : Producer._> + { + private readonly IObservable _source; + private readonly Func> _subjectSelector; + private readonly Func, IObservable> _selector; + + public Multicast(IObservable source, Func> subjectSelector, Func, IObservable> selector) + { + _source = source; + _subjectSelector = subjectSelector; + _selector = selector; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : IdentitySink + { + private SingleAssignmentDisposableValue _connection; + + public _(IObserver observer) + : base(observer) + { + } + + public void Run(Multicast parent) + { + IObservable observable; + IConnectableObservable connectable; + + try + { + var subject = parent._subjectSelector(); + connectable = new ConnectableObservable(parent._source, subject); + observable = parent._selector(connectable); + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + Run(observable); + _connection.Disposable = connectable.Connect(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _connection.Dispose(); + } + + base.Dispose(disposing); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Never.cs b/LibExternal/System.Reactive/Linq/Observable/Never.cs new file mode 100644 index 0000000..59ea83f --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Never.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Never : IObservable + { + /// + /// The only instance for a TResult type: this source + /// is completely stateless and has a constant behavior. + /// + internal static readonly IObservable Default = new Never(); + + /// + /// No need for instantiating this more than once per TResult. + /// + private Never() + { + + } + + public IDisposable Subscribe(IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + return Disposable.Empty; + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Next.cs b/LibExternal/System.Reactive/Linq/Observable/Next.cs new file mode 100644 index 0000000..0f99d0c --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Next.cs @@ -0,0 +1,140 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics.CodeAnalysis; +using System.Threading; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Next : PushToPullAdapter + { + public Next(IObservable source) + : base(source) + { + } + + protected override PushToPullSink Run() => new _(); + + private sealed class _ : PushToPullSink + { + private readonly object _gate; + private readonly SemaphoreSlim _semaphore; + + public _() + { + _gate = new object(); + _semaphore = new SemaphoreSlim(0, 1); + } + + private bool _waiting; + private NotificationKind _kind; + private TSource? _value; + private Exception? _error; + + public override void OnNext(TSource value) + { + lock (_gate) + { + if (_waiting) + { + _value = value; + _kind = NotificationKind.OnNext; + _semaphore.Release(); + } + + _waiting = false; + } + } + + public override void OnError(Exception error) + { + Dispose(); + + lock (_gate) + { + // + // BREAKING CHANGE v2 > v1.x - Next doesn't block indefinitely when it reaches the end. + // + _error = error; + _kind = NotificationKind.OnError; + + if (_waiting) + { + _semaphore.Release(); + } + + _waiting = false; + } + } + + public override void OnCompleted() + { + Dispose(); + + lock (_gate) + { + // + // BREAKING CHANGE v2 > v1.x - Next doesn't block indefinitely when it reaches the end. + // + _kind = NotificationKind.OnCompleted; + + if (_waiting) + { + _semaphore.Release(); + } + + _waiting = false; + } + } + + public override bool TryMoveNext([MaybeNullWhen(false)] out TSource current) + { + var done = false; + + lock (_gate) + { + _waiting = true; + + // + // BREAKING CHANGE v2 > v1.x - Next doesn't block indefinitely when it reaches the end. + // + done = _kind != NotificationKind.OnNext; + } + + if (!done) + { + _semaphore.Wait(); + } + + // + // When we reach this point, we released the lock and got the next notification + // from the observer. We assume no concurrent calls to the TryMoveNext method + // are made (per general guidance on usage of IEnumerable). If the observer + // enters the lock again, it should have quit it first, causing _waiting to be + // set to false, hence future accesses of the lock won't set the _kind, _value, + // and _error fields, until TryMoveNext is entered again and _waiting is reset + // to true. In conclusion, the fields are stable for read below. + // + // Notice we rely on memory barrier acquire/release behavior due to the use of + // the semaphore, not the lock (we're still under the lock when we release the + // semaphore in the On* methods!). + // + switch (_kind) + { + case NotificationKind.OnNext: + current = _value!; + return true; + case NotificationKind.OnError: + _error!.Throw(); + break; + case NotificationKind.OnCompleted: + break; + } + + current = default; + return false; + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/OfType.cs b/LibExternal/System.Reactive/Linq/Observable/OfType.cs new file mode 100644 index 0000000..6932677 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/OfType.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class OfType : Producer._> + { + private readonly IObservable _source; + + public OfType(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(TSource value) + { + if (value is TResult v) + { + ForwardOnNext(v); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/OnErrorResumeNext.cs b/LibExternal/System.Reactive/Linq/Observable/OnErrorResumeNext.cs new file mode 100644 index 0000000..85f36d8 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/OnErrorResumeNext.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class OnErrorResumeNext : Producer._> + { + private readonly IEnumerable> _sources; + + public OnErrorResumeNext(IEnumerable> sources) + { + _sources = sources; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_sources); + + internal sealed class _ : TailRecursiveSink + { + public _(IObserver observer) + : base(observer) + { + } + + protected override IEnumerable>? Extract(IObservable source) + { + if (source is OnErrorResumeNext oern) + { + return oern._sources; + } + + return null; + } + + public override void OnError(Exception error) + { + Recurse(); + } + + public override void OnCompleted() + { + Recurse(); + } + + protected override bool Fail(Exception error) + { + // + // Note that the invocation of _recurse in OnError will + // cause the next MoveNext operation to be enqueued, so + // we will still return to the caller immediately. + // + OnError(error); + return true; + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/PushToPullAdapter.cs b/LibExternal/System.Reactive/Linq/Observable/PushToPullAdapter.cs new file mode 100644 index 0000000..fd79504 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/PushToPullAdapter.cs @@ -0,0 +1,87 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal abstract class PushToPullAdapter : IEnumerable + { + private readonly IObservable _source; + + protected PushToPullAdapter(IObservable source) + { + _source = source; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public IEnumerator GetEnumerator() + { + var res = Run(); + res.SetUpstream(_source.SubscribeSafe(res)); + return res; + } + + protected abstract PushToPullSink Run(); + } + + internal abstract class PushToPullSink : IObserver, IEnumerator + { + private SingleAssignmentDisposableValue _upstream; + + public abstract void OnNext(TSource value); + public abstract void OnError(Exception error); + public abstract void OnCompleted(); + + public abstract bool TryMoveNext([MaybeNullWhen(false)] out TResult current); + + private bool _done; + + public bool MoveNext() + { + if (!_done) + { + if (TryMoveNext(out var current)) + { + Current = current; + return true; + } + + _done = true; + Dispose(); + } + + return false; + } + +#nullable disable // NB: Matches the protocol around accessing Current only if MoveNext returns true. + public TResult Current + { + get; + private set; + } + + object IEnumerator.Current => Current; +#nullable restore + + public void Reset() + { + throw new NotSupportedException(); + } + + public void Dispose() + { + _upstream.Dispose(); + } + + public void SetUpstream(IDisposable d) + { + _upstream.Disposable = d; + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Range.cs b/LibExternal/System.Reactive/Linq/Observable/Range.cs new file mode 100644 index 0000000..3b4a193 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Range.cs @@ -0,0 +1,127 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class RangeRecursive : Producer + { + private readonly int _start; + private readonly int _count; + private readonly IScheduler _scheduler; + + public RangeRecursive(int start, int count, IScheduler scheduler) + { + _start = start; + _count = count; + _scheduler = scheduler; + } + + protected override RangeSink CreateSink(IObserver observer) => new(_start, _count, observer); + + protected override void Run(RangeSink sink) => sink.Run(_scheduler); + + internal sealed class RangeSink : IdentitySink + { + private readonly int _end; + private int _index; + private MultipleAssignmentDisposableValue _task; + + public RangeSink(int start, int count, IObserver observer) + : base(observer) + { + _index = start; + _end = start + count; + } + + public void Run(IScheduler scheduler) + { + var first = scheduler.Schedule(this, static (innerScheduler, @this) => @this.LoopRec(innerScheduler)); + _task.TrySetFirst(first); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (disposing) + { + _task.Dispose(); + } + } + + private IDisposable LoopRec(IScheduler scheduler) + { + var idx = _index; + + if (idx != _end) + { + _index = idx + 1; + ForwardOnNext(idx); + var next = scheduler.Schedule(this, static (innerScheduler, @this) => @this.LoopRec(innerScheduler)); + _task.Disposable = next; + } + else + { + ForwardOnCompleted(); + } + + return Disposable.Empty; + } + } + } + + internal sealed class RangeLongRunning : Producer + { + private readonly int _start; + private readonly int _count; + private readonly ISchedulerLongRunning _scheduler; + + public RangeLongRunning(int start, int count, ISchedulerLongRunning scheduler) + { + _start = start; + _count = count; + _scheduler = scheduler; + } + + protected override RangeSink CreateSink(IObserver observer) => new(_start, _count, observer); + + protected override void Run(RangeSink sink) => sink.Run(_scheduler); + + internal sealed class RangeSink : IdentitySink + { + private readonly int _end; + private readonly int _index; + + public RangeSink(int start, int count, IObserver observer) + : base(observer) + { + _index = start; + _end = start + count; + } + + public void Run(ISchedulerLongRunning scheduler) + { + SetUpstream(scheduler.ScheduleLongRunning(this, static (@this, cancel) => @this.Loop(cancel))); + } + + private void Loop(ICancelable cancel) + { + var idx = _index; + var end = _end; + + while (!cancel.IsDisposed && idx != end) + { + ForwardOnNext(idx++); + } + + if (!cancel.IsDisposed) + { + ForwardOnCompleted(); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/RefCount.cs b/LibExternal/System.Reactive/Linq/Observable/RefCount.cs new file mode 100644 index 0000000..686b3ee --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/RefCount.cs @@ -0,0 +1,218 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reactive.Disposables; +using System.Reactive.Subjects; +using System.Threading; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class RefCount + { + internal sealed class Eager : Producer + { + private readonly IConnectableObservable _source; + + private readonly object _gate = new(); + + /// + /// Contains the current active connection's state or null + /// if no connection is active at the moment. + /// Should be manipulated while holding the lock. + /// + private RefConnection? _connection; + + private readonly int _minObservers; + + public Eager(IConnectableObservable source, int minObservers) + { + _source = source; + _minObservers = minObservers; + } + + protected override _ CreateSink(IObserver observer) => new(observer, this); + + protected override void Run(_ sink) => sink.Run(); + + internal sealed class _ : IdentitySink + { + private readonly Eager _parent; + + /// + /// Contains the connection reference the downstream observer + /// has subscribed to. Its purpose is to + /// avoid subscribing, connecting and disconnecting + /// while holding a lock. + /// + private RefConnection? _targetConnection; + + public _(IObserver observer, Eager parent) + : base(observer) + { + _parent = parent; + } + + public void Run() + { + bool doConnect; + RefConnection? conn; + + lock (_parent._gate) + { + // get the active connection state + conn = _parent._connection; + + // if null, a new connection should be established + if (conn == null) + { + conn = new RefConnection(); + // make it the active one + _parent._connection = conn; + } + + // this is the first observer, then connect + doConnect = ++conn._count == _parent._minObservers; + + // save the current connection for this observer + _targetConnection = conn; + } + + // subscribe to the source first + Run(_parent._source); + + // then connect the source if necessary + if (doConnect && !conn._disposable.IsDisposed) + { + // this makes sure if the connection ends synchronously + // only the currently known connection is affected + // and a connection from a concurrent reconnection won't + // interfere + conn._disposable.Disposable = _parent._source.Connect(); + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + // get and forget the saved connection + var targetConnection = _targetConnection!; // NB: Always set by Run prior to calling Dispose, and base class hardens protects against double-dispose. + _targetConnection = null; + + lock (_parent._gate) + { + // if the current connection is no longer the saved connection + // or the counter hasn't reached zero yet + if (targetConnection != _parent._connection + || --targetConnection._count != 0) + { + // nothing to do. + return; + } + + // forget the current connection + _parent._connection = null; + } + + // disconnect + targetConnection._disposable.Dispose(); + } + } + } + + /// + /// Holds an individual connection state: the observer count and + /// the connection's IDisposable. + /// + private sealed class RefConnection + { + internal int _count; + internal SingleAssignmentDisposableValue _disposable; + } + } + + internal sealed class Lazy : Producer + { + private readonly object _gate; + private readonly IScheduler _scheduler; + private readonly TimeSpan _disconnectTime; + private readonly IConnectableObservable _source; + private readonly int _minObservers; + + private IDisposable? _serial; + private int _count; + private IDisposable? _connectableSubscription; + + public Lazy(IConnectableObservable source, TimeSpan disconnectTime, IScheduler scheduler, int minObservers) + { + _source = source; + _gate = new object(); + _disconnectTime = disconnectTime; + _scheduler = scheduler; + _minObservers = minObservers; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : IdentitySink + { + public _(IObserver observer) + : base(observer) + { + } + + public void Run(Lazy parent) + { + var subscription = parent._source.SubscribeSafe(this); + + lock (parent._gate) + { + if (++parent._count == parent._minObservers) + { + parent._connectableSubscription ??= parent._source.Connect(); + + Disposable.TrySetSerial(ref parent._serial, new SingleAssignmentDisposable()); + } + } + + SetUpstream(Disposable.Create( + (parent, subscription), + static tuple => + { + var (closureParent, closureSubscription) = tuple; + + closureSubscription.Dispose(); + + lock (closureParent._gate) + { + if (--closureParent._count == 0) + { + // NB: _serial is guaranteed to be set by TrySetSerial earlier on. + var cancelable = (SingleAssignmentDisposable)Volatile.Read(ref closureParent._serial)!; + + cancelable.Disposable = closureParent._scheduler.ScheduleAction((cancelable, closureParent), closureParent._disconnectTime, static tuple2 => + { + lock (tuple2.closureParent._gate) + { + if (ReferenceEquals(Volatile.Read(ref tuple2.closureParent._serial), tuple2.cancelable)) + { + // NB: _connectableSubscription is guaranteed to be set above, and Disposable.Create protects against double-dispose. + tuple2.closureParent._connectableSubscription!.Dispose(); + tuple2.closureParent._connectableSubscription = null; + } + } + }); + } + } + })); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Repeat.cs b/LibExternal/System.Reactive/Linq/Observable/Repeat.cs new file mode 100644 index 0000000..7188032 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Repeat.cs @@ -0,0 +1,235 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class Repeat + { + internal sealed class ForeverRecursive : Producer + { + private readonly TResult _value; + private readonly IScheduler _scheduler; + + public ForeverRecursive(TResult value, IScheduler scheduler) + { + _value = value; + _scheduler = scheduler; + } + + protected override _ CreateSink(IObserver observer) => new(_value, observer); + + protected override void Run(_ sink) => sink.Run(_scheduler); + + internal sealed class _ : IdentitySink + { + private readonly TResult _value; + + private MultipleAssignmentDisposableValue _task; + + public _(TResult value, IObserver observer) + : base(observer) + { + _value = value; + } + + public void Run(IScheduler scheduler) + { + var first = scheduler.Schedule(this, static (innerScheduler, @this) => @this.LoopRecInf(innerScheduler)); + _task.TrySetFirst(first); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _task.Dispose(); + } + } + + private IDisposable LoopRecInf(IScheduler scheduler) + { + ForwardOnNext(_value); + + var next = scheduler.Schedule(this, static (innerScheduler, @this) => @this.LoopRecInf(innerScheduler)); + _task.Disposable = next; + + return Disposable.Empty; + } + } + } + + internal sealed class ForeverLongRunning : Producer + { + private readonly TResult _value; + private readonly ISchedulerLongRunning _scheduler; + + public ForeverLongRunning(TResult value, ISchedulerLongRunning scheduler) + { + _value = value; + _scheduler = scheduler; + } + + protected override _ CreateSink(IObserver observer) => new(_value, observer); + + protected override void Run(_ sink) => sink.Run(_scheduler); + + internal sealed class _ : IdentitySink + { + private readonly TResult _value; + + public _(TResult value, IObserver observer) + : base(observer) + { + _value = value; + } + + public void Run(ISchedulerLongRunning longRunning) + { + SetUpstream(longRunning.ScheduleLongRunning(this, static (@this, c) => @this.LoopInf(c))); + } + + private void LoopInf(ICancelable cancel) + { + var value = _value; + while (!cancel.IsDisposed) + { + ForwardOnNext(value); + } + + Dispose(); + } + } + } + + internal sealed class CountRecursive : Producer + { + private readonly TResult _value; + private readonly IScheduler _scheduler; + private readonly int _repeatCount; + + public CountRecursive(TResult value, int repeatCount, IScheduler scheduler) + { + _value = value; + _scheduler = scheduler; + _repeatCount = repeatCount; + } + + protected override _ CreateSink(IObserver observer) => new(_value, _repeatCount, observer); + + protected override void Run(_ sink) => sink.Run(_scheduler); + + internal sealed class _ : IdentitySink + { + private readonly TResult _value; + + private int _remaining; + + private MultipleAssignmentDisposableValue _task; + + public _(TResult value, int repeatCount, IObserver observer) + : base(observer) + { + _value = value; + _remaining = repeatCount; + } + + public void Run(IScheduler scheduler) + { + var first = scheduler.Schedule(this, static (innerScheduler, @this) => @this.LoopRec(innerScheduler)); + _task.TrySetFirst(first); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _task.Dispose(); + } + } + + private IDisposable LoopRec(IScheduler scheduler) + { + var remaining = _remaining; + if (remaining > 0) + { + ForwardOnNext(_value); + _remaining = --remaining; + } + + if (remaining == 0) + { + ForwardOnCompleted(); + } + else + { + var next = scheduler.Schedule(this, static (innerScheduler, @this) => @this.LoopRec(innerScheduler)); + _task.Disposable = next; + } + + return Disposable.Empty; + } + } + } + + internal sealed class CountLongRunning : Producer + { + private readonly TResult _value; + private readonly ISchedulerLongRunning _scheduler; + private readonly int _repeatCount; + + public CountLongRunning(TResult value, int repeatCount, ISchedulerLongRunning scheduler) + { + _value = value; + _scheduler = scheduler; + _repeatCount = repeatCount; + } + + protected override _ CreateSink(IObserver observer) => new(_value, _repeatCount, observer); + + protected override void Run(_ sink) => sink.Run(_scheduler); + + internal sealed class _ : IdentitySink + { + private readonly TResult _value; + private readonly int _remaining; + + public _(TResult value, int remaining, IObserver observer) + : base(observer) + { + _value = value; + _remaining = remaining; + } + + public void Run(ISchedulerLongRunning longRunning) + { + SetUpstream(longRunning.ScheduleLongRunning(this, static (@this, cancel) => @this.Loop(cancel))); + } + + private void Loop(ICancelable cancel) + { + var value = _value; + var n = _remaining; + + while (n > 0 && !cancel.IsDisposed) + { + ForwardOnNext(value); + n--; + } + + if (!cancel.IsDisposed) + { + ForwardOnCompleted(); + } + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/RepeatWhen.cs b/LibExternal/System.Reactive/Linq/Observable/RepeatWhen.cs new file mode 100644 index 0000000..9e03819 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/RepeatWhen.cs @@ -0,0 +1,161 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; +using System.Reactive.Subjects; +using System.Threading; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class RepeatWhen : IObservable + { + private readonly IObservable _source; + private readonly Func, IObservable> _handler; + + internal RepeatWhen(IObservable source, Func, IObservable> handler) + { + _source = source; + _handler = handler; + } + + public IDisposable Subscribe(IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + var completeSignals = new Subject(); + + IObservable redo; + + try + { + redo = _handler(completeSignals); + + if (redo == null) + { +#pragma warning disable CA2201 // (Do not raise reserved exception types.) Backwards compatibility prevents us from complying. + throw new NullReferenceException("The handler returned a null IObservable"); +#pragma warning restore CA2201 + } + } + catch (Exception ex) + { + observer.OnError(ex); + return Disposable.Empty; + } + + var parent = new MainObserver(observer, _source, new RedoSerializedObserver(completeSignals)); + + var d = redo.SubscribeSafe(parent.HandlerConsumer); + parent._handlerUpstream.Disposable = d; + + parent.HandlerNext(); + + return parent; + } + + private sealed class MainObserver : Sink, IObserver + { + private readonly IObservable _source; + private readonly IObserver _completeSignal; + + internal readonly HandlerObserver HandlerConsumer; + + internal SingleAssignmentDisposableValue _handlerUpstream; + + private IDisposable? _upstream; + private int _trampoline; + private int _halfSerializer; + private Exception? _error; + + internal MainObserver(IObserver downstream, IObservable source, IObserver completeSignal) : base(downstream) + { + _source = source; + _completeSignal = completeSignal; + HandlerConsumer = new HandlerObserver(this); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + Disposable.Dispose(ref _upstream); + _handlerUpstream.Dispose(); + } + + base.Dispose(disposing); + } + + public void OnCompleted() + { + if (Disposable.TrySetSerial(ref _upstream, null)) + { + // + // NB: Unfortunately this thing slipped in using `object` rather than `Unit`, which is our type used to represent nothing, + // so we have to stick with it and just let a `null` go in here. Users are supposed to ignore the elements produced, + // which `Unit` is making obvious since there's only one value. However, we're stuck here for compat reasons. + // + + _completeSignal.OnNext(null!); + } + } + + public void OnError(Exception error) + { + HalfSerializer.ForwardOnError(this, error, ref _halfSerializer, ref _error); + } + + public void OnNext(T value) + { + HalfSerializer.ForwardOnNext(this, value, ref _halfSerializer, ref _error); + } + + private void HandlerError(Exception error) + { + HalfSerializer.ForwardOnError(this, error, ref _halfSerializer, ref _error); + } + + private void HandlerComplete() + { + HalfSerializer.ForwardOnCompleted(this, ref _halfSerializer, ref _error); + } + + internal void HandlerNext() + { + if (Interlocked.Increment(ref _trampoline) == 1) + { + do + { + var sad = new SingleAssignmentDisposable(); + if (Interlocked.CompareExchange(ref _upstream, sad, null) != null) + { + return; + } + + sad.Disposable = _source.SubscribeSafe(this); + } + while (Interlocked.Decrement(ref _trampoline) != 0); + } + } + + internal sealed class HandlerObserver : IObserver + { + private readonly MainObserver _main; + + internal HandlerObserver(MainObserver main) + { + _main = main; + } + + public void OnCompleted() => _main.HandlerComplete(); + + public void OnError(Exception error) => _main.HandlerError(error); + + public void OnNext(U value) => _main.HandlerNext(); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/RetryWhen.cs b/LibExternal/System.Reactive/Linq/Observable/RetryWhen.cs new file mode 100644 index 0000000..f7ee394 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/RetryWhen.cs @@ -0,0 +1,256 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Concurrent; +using System.Reactive.Disposables; +using System.Reactive.Subjects; +using System.Threading; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class RetryWhen : IObservable + { + private readonly IObservable _source; + private readonly Func, IObservable> _handler; + + internal RetryWhen(IObservable source, Func, IObservable> handler) + { + _source = source; + _handler = handler; + } + + public IDisposable Subscribe(IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + var errorSignals = new Subject(); + + IObservable redo; + + try + { + redo = _handler(errorSignals); + + if (redo == null) + { +#pragma warning disable CA2201 // (Do not raise reserved exception types.) Backwards compatibility prevents us from complying. + throw new NullReferenceException("The handler returned a null IObservable"); +#pragma warning restore CA2201 + } + } + catch (Exception ex) + { + observer.OnError(ex); + return Disposable.Empty; + } + + var parent = new MainObserver(observer, _source, new RedoSerializedObserver(errorSignals)); + + var d = redo.SubscribeSafe(parent.HandlerConsumer); + parent.HandlerUpstream.Disposable = d; + + parent.HandlerNext(); + + return parent; + } + + private sealed class MainObserver : Sink, IObserver + { + private readonly IObservable _source; + private readonly IObserver _errorSignal; + + internal readonly HandlerObserver HandlerConsumer; + private IDisposable? _upstream; + internal SingleAssignmentDisposableValue HandlerUpstream; + private int _trampoline; + private int _halfSerializer; + private Exception? _error; + + internal MainObserver(IObserver downstream, IObservable source, IObserver errorSignal) : base(downstream) + { + _source = source; + _errorSignal = errorSignal; + HandlerConsumer = new HandlerObserver(this); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + Disposable.Dispose(ref _upstream); + HandlerUpstream.Dispose(); + } + + base.Dispose(disposing); + } + + public void OnCompleted() + { + HalfSerializer.ForwardOnCompleted(this, ref _halfSerializer, ref _error); + } + + public void OnError(Exception error) + { + if (Disposable.TrySetSerial(ref _upstream, null)) + { + _errorSignal.OnNext(error); + } + } + + public void OnNext(T value) + { + HalfSerializer.ForwardOnNext(this, value, ref _halfSerializer, ref _error); + } + + private void HandlerError(Exception error) + { + HalfSerializer.ForwardOnError(this, error, ref _halfSerializer, ref _error); + } + + private void HandlerComplete() + { + HalfSerializer.ForwardOnCompleted(this, ref _halfSerializer, ref _error); + } + + internal void HandlerNext() + { + if (Interlocked.Increment(ref _trampoline) == 1) + { + do + { + var sad = new SingleAssignmentDisposable(); + if (Disposable.TrySetSingle(ref _upstream, sad) != TrySetSingleResult.Success) + { + return; + } + + sad.Disposable = _source.SubscribeSafe(this); + } + while (Interlocked.Decrement(ref _trampoline) != 0); + } + } + + internal sealed class HandlerObserver : IObserver + { + private readonly MainObserver _main; + + internal HandlerObserver(MainObserver main) + { + _main = main; + } + + public void OnCompleted() + { + _main.HandlerComplete(); + } + + public void OnError(Exception error) + { + _main.HandlerError(error); + } + + public void OnNext(U value) + { + _main.HandlerNext(); + } + } + } + } + + internal sealed class RedoSerializedObserver : IObserver + { +#pragma warning disable CA2201 // (Do not raise reserved exception types.) This is a sentinel, and is not thrown, so there's no need for it to be anything else. + private static readonly Exception SignaledIndicator = new(); +#pragma warning restore CA2201 + + private readonly IObserver _downstream; + private readonly ConcurrentQueue _queue; + + private int _wip; + private Exception? _terminalException; + + internal RedoSerializedObserver(IObserver downstream) + { + _downstream = downstream; + _queue = new ConcurrentQueue(); + } + + public void OnCompleted() + { + if (Interlocked.CompareExchange(ref _terminalException, ExceptionHelper.Terminated, null) == null) + { + Drain(); + } + } + + public void OnError(Exception error) + { + if (Interlocked.CompareExchange(ref _terminalException, error, null) == null) + { + Drain(); + } + } + + public void OnNext(X value) + { + _queue.Enqueue(value); + Drain(); + } + + private void Clear() + { + while (_queue.TryDequeue(out _)) + { + } + } + + private void Drain() + { + if (Interlocked.Increment(ref _wip) != 1) + { + return; + } + + var missed = 1; + + for (; ; ) + { + var ex = Volatile.Read(ref _terminalException); + if (ex != null) + { + if (ex != SignaledIndicator) + { + Interlocked.Exchange(ref _terminalException, SignaledIndicator); + if (ex != ExceptionHelper.Terminated) + { + _downstream.OnError(ex); + } + else + { + _downstream.OnCompleted(); + } + } + Clear(); + } + else + { + while (_queue.TryDequeue(out var item)) + { + _downstream.OnNext(item); + } + } + + + missed = Interlocked.Add(ref _wip, -missed); + if (missed == 0) + { + break; + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Return.cs b/LibExternal/System.Reactive/Linq/Observable/Return.cs new file mode 100644 index 0000000..1cfbce4 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Return.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Return : Producer._> + { + private readonly TResult _value; + private readonly IScheduler _scheduler; + + public Return(TResult value, IScheduler scheduler) + { + _value = value; + _scheduler = scheduler; + } + + protected override _ CreateSink(IObserver observer) => new(_value, observer); + + protected override void Run(_ sink) => sink.Run(_scheduler); + + internal sealed class _ : IdentitySink + { + private readonly TResult _value; + + public _(TResult value, IObserver observer) + : base(observer) + { + _value = value; + } + + public void Run(IScheduler scheduler) + { + SetUpstream(scheduler.ScheduleAction(this, static @this => @this.Invoke())); + } + + private void Invoke() + { + ForwardOnNext(_value); + ForwardOnCompleted(); + } + } + } + + // There is no need for a full Producer/IdentitySink as there is no + // way to stop a first task running on the immediate scheduler + // as it is always synchronous. + internal sealed class ReturnImmediate : BasicProducer + { + private readonly TSource _value; + + public ReturnImmediate(TSource value) + { + _value = value; + } + + protected override IDisposable Run(IObserver observer) + { + observer.OnNext(_value); + observer.OnCompleted(); + return Disposable.Empty; + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Sample.cs b/LibExternal/System.Reactive/Linq/Observable/Sample.cs new file mode 100644 index 0000000..66422ef --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Sample.cs @@ -0,0 +1,248 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Sample : Producer._> + { + private readonly IObservable _source; + private readonly IObservable _sampler; + + public Sample(IObservable source, IObservable sampler) + { + _source = source; + _sampler = sampler; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : IdentitySink + { + private readonly object _gate = new(); + + public _(IObserver observer) + : base(observer) + { + } + + private SingleAssignmentDisposableValue _sourceDisposable; + private SingleAssignmentDisposableValue _samplerDisposable; + + private bool _hasValue; + private TSource? _value; + private bool _sourceAtEnd; + private bool _samplerAtEnd; + + public void Run(Sample parent) + { + _sourceDisposable.Disposable = parent._source.SubscribeSafe(this); + + _samplerDisposable.Disposable = parent._sampler.SubscribeSafe(new SampleObserver(this)); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _sourceDisposable.Dispose(); + _samplerDisposable.Dispose(); + } + + base.Dispose(disposing); + } + + public override void OnNext(TSource value) + { + lock (_gate) + { + _hasValue = true; + _value = value; + } + } + + public override void OnError(Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + lock (_gate) + { + _sourceAtEnd = true; + + if (_samplerAtEnd) + { + ForwardOnCompleted(); + } + else + { + _sourceDisposable.Dispose(); + } + } + } + + private sealed class SampleObserver : IObserver + { + private readonly _ _parent; + + public SampleObserver(_ parent) + { + _parent = parent; + } + + public void OnNext(TSample value) + { + lock (_parent._gate) + { + if (_parent._hasValue) + { + _parent._hasValue = false; + _parent.ForwardOnNext(_parent._value!); + } + + if (_parent._sourceAtEnd) + { + _parent.ForwardOnCompleted(); + } + } + } + + public void OnError(Exception error) + { + // BREAKING CHANGE v2 > v1.x - This error used to be swallowed + lock (_parent._gate) + { + _parent.ForwardOnError(error); + } + } + + public void OnCompleted() + { + lock (_parent._gate) + { + _parent._samplerAtEnd = true; + + if (_parent._hasValue) + { + _parent._hasValue = false; + _parent.ForwardOnNext(_parent._value!); + } + + if (_parent._sourceAtEnd) + { + _parent.ForwardOnCompleted(); + } + else + { + _parent._samplerDisposable.Dispose(); + } + } + } + } + } + } + + internal sealed class Sample : Producer._> + { + private readonly IObservable _source; + private readonly TimeSpan _interval; + private readonly IScheduler _scheduler; + + public Sample(IObservable source, TimeSpan interval, IScheduler scheduler) + { + _source = source; + _interval = interval; + _scheduler = scheduler; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : IdentitySink + { + private readonly object _gate = new(); + + public _(IObserver observer) + : base(observer) + { + } + + private SingleAssignmentDisposableValue _sourceDisposable; + + private bool _hasValue; + private TSource? _value; + private bool _atEnd; + + public void Run(Sample parent) + { + _sourceDisposable.Disposable = parent._source.SubscribeSafe(this); + + SetUpstream(parent._scheduler.SchedulePeriodic(parent._interval, Tick)); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _sourceDisposable.Dispose(); + } + + base.Dispose(disposing); + } + + private void Tick() + { + lock (_gate) + { + if (_hasValue) + { + _hasValue = false; + ForwardOnNext(_value!); + } + + if (_atEnd) + { + ForwardOnCompleted(); + } + } + } + + public override void OnNext(TSource value) + { + lock (_gate) + { + _hasValue = true; + _value = value; + } + } + + public override void OnError(Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + lock (_gate) + { + _atEnd = true; + _sourceDisposable.Dispose(); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Scan.cs b/LibExternal/System.Reactive/Linq/Observable/Scan.cs new file mode 100644 index 0000000..f4593df --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Scan.cs @@ -0,0 +1,105 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Scan : Producer._> + { + private readonly IObservable _source; + private readonly TAccumulate _seed; + private readonly Func _accumulator; + + public Scan(IObservable source, TAccumulate seed, Func accumulator) + { + _source = source; + _seed = seed; + _accumulator = accumulator; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private readonly Func _accumulator; + private TAccumulate _accumulation; + + public _(Scan parent, IObserver observer) + : base(observer) + { + _accumulator = parent._accumulator; + _accumulation = parent._seed; + } + + public override void OnNext(TSource value) + { + try + { + _accumulation = _accumulator(_accumulation, value); + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + ForwardOnNext(_accumulation); + } + } + } + + internal sealed class Scan : Producer._> + { + private readonly IObservable _source; + private readonly Func _accumulator; + + public Scan(IObservable source, Func accumulator) + { + _source = source; + _accumulator = accumulator; + } + + protected override _ CreateSink(IObserver observer) => new(_accumulator, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly Func _accumulator; + private TSource? _accumulation; + private bool _hasAccumulation; + + public _(Func accumulator, IObserver observer) + : base(observer) + { + _accumulator = accumulator; + } + + public override void OnNext(TSource value) + { + try + { + if (_hasAccumulation) + { + _accumulation = _accumulator(_accumulation!, value); + } + else + { + _accumulation = value; + _hasAccumulation = true; + } + } + catch (Exception exception) + { + ForwardOnError(exception); + + return; + } + + ForwardOnNext(_accumulation); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Select.cs b/LibExternal/System.Reactive/Linq/Observable/Select.cs new file mode 100644 index 0000000..0fccea6 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Select.cs @@ -0,0 +1,96 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class Select + { + internal sealed class Selector : Producer + { + private readonly IObservable _source; + private readonly Func _selector; + + public Selector(IObservable source, Func selector) + { + _source = source; + _selector = selector; + } + + protected override _ CreateSink(IObserver observer) => new(_selector, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private readonly Func _selector; + + public _(Func selector, IObserver observer) + : base(observer) + { + _selector = selector; + } + + public override void OnNext(TSource value) + { + TResult result; + try + { + result = _selector(value); + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + ForwardOnNext(result); + } + } + } + + internal sealed class SelectorIndexed : Producer + { + private readonly IObservable _source; + private readonly Func _selector; + + public SelectorIndexed(IObservable source, Func selector) + { + _source = source; + _selector = selector; + } + + protected override _ CreateSink(IObserver observer) => new(_selector, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private readonly Func _selector; + private int _index; + + public _(Func selector, IObserver observer) + : base(observer) + { + _selector = selector; + } + + public override void OnNext(TSource value) + { + TResult result; + try + { + result = _selector(value, checked(_index++)); + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + ForwardOnNext(result); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/SelectMany.cs b/LibExternal/System.Reactive/Linq/Observable/SelectMany.cs new file mode 100644 index 0000000..17c19f8 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/SelectMany.cs @@ -0,0 +1,1735 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Concurrency; +using System.Reactive.Disposables; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class SelectMany + { + internal sealed class ObservableSelector : Producer + { + private readonly IObservable _source; + private readonly Func> _collectionSelector; + private readonly Func _resultSelector; + + public ObservableSelector(IObservable source, Func> collectionSelector, Func resultSelector) + { + _source = source; + _collectionSelector = collectionSelector; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private readonly object _gate = new(); + private readonly CompositeDisposable _group = new(); + + private readonly Func> _collectionSelector; + private readonly Func _resultSelector; + + public _(ObservableSelector parent, IObserver observer) + : base(observer) + { + _collectionSelector = parent._collectionSelector; + _resultSelector = parent._resultSelector; + } + + private volatile bool _isStopped; + + public override void OnNext(TSource value) + { + IObservable collection; + + try + { + collection = _collectionSelector(value); + } + catch (Exception ex) + { + lock (_gate) + { + ForwardOnError(ex); + } + return; + } + + var innerObserver = new InnerObserver(this, value); + _group.Add(innerObserver); + innerObserver.SetResource(collection.SubscribeSafe(innerObserver)); + } + + public override void OnError(Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + _isStopped = true; + if (_group.Count == 0) + { + // + // Notice there can be a race between OnCompleted of the source and any + // of the inner sequences, where both see _group.Count == 1, and one is + // waiting for the lock. There won't be a double OnCompleted observation + // though, because the call to Dispose silences the observer by swapping + // in a NopObserver. + // + lock (_gate) + { + ForwardOnCompleted(); + } + } + else + { + DisposeUpstream(); + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _group.Dispose(); + } + } + + private sealed class InnerObserver : SafeObserver + { + private readonly _ _parent; + private readonly TSource _value; + + public InnerObserver(_ parent, TSource value) + { + _parent = parent; + _value = value; + } + + public override void OnNext(TCollection value) + { + TResult res; + + try + { + res = _parent._resultSelector(_value, value); + } + catch (Exception ex) + { + lock (_parent._gate) + { + _parent.ForwardOnError(ex); + } + return; + } + + lock (_parent._gate) + { + _parent.ForwardOnNext(res); + } + } + + public override void OnError(Exception error) + { + lock (_parent._gate) + { + _parent.ForwardOnError(error); + } + } + + public override void OnCompleted() + { + _parent._group.Remove(this); + if (_parent._isStopped && _parent._group.Count == 0) + { + // + // Notice there can be a race between OnCompleted of the source and any + // of the inner sequences, where both see _group.Count == 1, and one is + // waiting for the lock. There won't be a double OnCompleted observation + // though, because the call to Dispose silences the observer by swapping + // in a NopObserver. + // + lock (_parent._gate) + { + _parent.ForwardOnCompleted(); + } + } + } + } + } + } + + internal sealed class ObservableSelectorIndexed : Producer + { + private readonly IObservable _source; + private readonly Func> _collectionSelector; + private readonly Func _resultSelector; + + public ObservableSelectorIndexed(IObservable source, Func> collectionSelector, Func resultSelector) + { + _source = source; + _collectionSelector = collectionSelector; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private readonly object _gate = new(); + private readonly CompositeDisposable _group = new(); + + private readonly Func> _collectionSelector; + private readonly Func _resultSelector; + + public _(ObservableSelectorIndexed parent, IObserver observer) + : base(observer) + { + _collectionSelector = parent._collectionSelector; + _resultSelector = parent._resultSelector; + } + + private volatile bool _isStopped; + private int _index; + + public override void OnNext(TSource value) + { + int index; + IObservable collection; + + try + { + index = checked(_index++); + collection = _collectionSelector(value, index); + } + catch (Exception ex) + { + lock (_gate) + { + ForwardOnError(ex); + } + return; + } + + var innerObserver = new InnerObserver(this, value, index); + _group.Add(innerObserver); + innerObserver.SetResource(collection.SubscribeSafe(innerObserver)); + } + + public override void OnError(Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + _isStopped = true; + if (_group.Count == 0) + { + // + // Notice there can be a race between OnCompleted of the source and any + // of the inner sequences, where both see _group.Count == 1, and one is + // waiting for the lock. There won't be a double OnCompleted observation + // though, because the call to Dispose silences the observer by swapping + // in a NopObserver. + // + lock (_gate) + { + ForwardOnCompleted(); + } + } + else + { + DisposeUpstream(); + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _group.Dispose(); + } + } + + private sealed class InnerObserver : SafeObserver + { + private readonly _ _parent; + private readonly TSource _value; + private readonly int _valueIndex; + + public InnerObserver(_ parent, TSource value, int index) + { + _parent = parent; + _value = value; + _valueIndex = index; + } + + private int _index; + + public override void OnNext(TCollection value) + { + TResult res; + + try + { + res = _parent._resultSelector(_value, _valueIndex, value, checked(_index++)); + } + catch (Exception ex) + { + lock (_parent._gate) + { + _parent.ForwardOnError(ex); + } + return; + } + + lock (_parent._gate) + { + _parent.ForwardOnNext(res); + } + } + + public override void OnError(Exception error) + { + lock (_parent._gate) + { + _parent.ForwardOnError(error); + } + } + + public override void OnCompleted() + { + _parent._group.Remove(this); + if (_parent._isStopped && _parent._group.Count == 0) + { + // + // Notice there can be a race between OnCompleted of the source and any + // of the inner sequences, where both see _group.Count == 1, and one is + // waiting for the lock. There won't be a double OnCompleted observation + // though, because the call to Dispose silences the observer by swapping + // in a NopObserver. + // + lock (_parent._gate) + { + _parent.ForwardOnCompleted(); + } + } + } + } + } + } + + internal sealed class EnumerableSelector : Producer + { + private readonly IObservable _source; + private readonly Func> _collectionSelector; + private readonly Func _resultSelector; + + public EnumerableSelector(IObservable source, Func> collectionSelector, Func resultSelector) + { + _source = source; + _collectionSelector = collectionSelector; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private readonly Func> _collectionSelector; + private readonly Func _resultSelector; + + public _(EnumerableSelector parent, IObserver observer) + : base(observer) + { + _collectionSelector = parent._collectionSelector; + _resultSelector = parent._resultSelector; + } + + public override void OnNext(TSource value) + { + IEnumerable xs; + try + { + xs = _collectionSelector(value); + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + IEnumerator e; + try + { + e = xs.GetEnumerator(); + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + using (e) + { + var hasNext = true; + + while (hasNext) + { + var current = default(TResult); + + try + { + hasNext = e.MoveNext(); + if (hasNext) + { + current = _resultSelector(value, e.Current); + } + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + if (hasNext) + { + ForwardOnNext(current!); + } + } + } + } + } + } + + internal sealed class EnumerableSelectorIndexed : Producer + { + private readonly IObservable _source; + private readonly Func> _collectionSelector; + private readonly Func _resultSelector; + + public EnumerableSelectorIndexed(IObservable source, Func> collectionSelector, Func resultSelector) + { + _source = source; + _collectionSelector = collectionSelector; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private readonly Func> _collectionSelector; + private readonly Func _resultSelector; + + public _(EnumerableSelectorIndexed parent, IObserver observer) + : base(observer) + { + _collectionSelector = parent._collectionSelector; + _resultSelector = parent._resultSelector; + } + + private int _index; + + public override void OnNext(TSource value) + { + int index; + + IEnumerable xs; + try + { + index = checked(_index++); + xs = _collectionSelector(value, index); + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + IEnumerator e; + try + { + e = xs.GetEnumerator(); + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + using (e) + { + var eIndex = 0; + var hasNext = true; + + while (hasNext) + { + var current = default(TResult); + + try + { + hasNext = e.MoveNext(); + if (hasNext) + { + current = _resultSelector(value, index, e.Current, checked(eIndex++)); + } + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + if (hasNext) + { + ForwardOnNext(current!); + } + } + } + } + } + } + + internal sealed class TaskSelector : Producer + { + private readonly IObservable _source; + private readonly Func> _collectionSelector; + private readonly Func _resultSelector; + + public TaskSelector(IObservable source, Func> collectionSelector, Func resultSelector) + { + _source = source; + _collectionSelector = collectionSelector; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private readonly object _gate = new(); + private readonly CancellationTokenSource _cancel = new(); + + private readonly Func> _collectionSelector; + private readonly Func _resultSelector; + + public _(TaskSelector parent, IObserver observer) + : base(observer) + { + _collectionSelector = parent._collectionSelector; + _resultSelector = parent._resultSelector; + } + + private volatile int _count; + + public override void Run(IObservable source) + { + _count = 1; + + base.Run(source); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _cancel.Cancel(); + } + base.Dispose(disposing); + } + + public override void OnNext(TSource value) + { + Task task; + try + { + Interlocked.Increment(ref _count); + task = _collectionSelector(value, _cancel.Token); + } + catch (Exception ex) + { + lock (_gate) + { + ForwardOnError(ex); + } + + return; + } + + if (task.IsCompleted) + { + OnCompletedTask(value, task); + } + else + { + task.ContinueWithState(static (t, tuple) => tuple.@this.OnCompletedTask(tuple.value, t), (@this: this, value), _cancel.Token); + } + } + + private void OnCompletedTask(TSource value, Task task) + { + switch (task.Status) + { + case TaskStatus.RanToCompletion: + { + TResult res; + try + { + res = _resultSelector(value, task.Result); + } + catch (Exception ex) + { + lock (_gate) + { + ForwardOnError(ex); + } + + return; + } + + lock (_gate) + { + ForwardOnNext(res); + } + + OnCompleted(); + + break; + } + + case TaskStatus.Faulted: + { + lock (_gate) + { + ForwardOnError(TaskHelpers.GetSingleException(task)); + } + + break; + } + case TaskStatus.Canceled: + { + if (!_cancel.IsCancellationRequested) + { + lock (_gate) + { + ForwardOnError(new TaskCanceledException(task)); + } + } + + break; + } + } + } + + public override void OnError(Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + if (Interlocked.Decrement(ref _count) == 0) + { + lock (_gate) + { + ForwardOnCompleted(); + } + } + } + } + } + + internal sealed class TaskSelectorIndexed : Producer + { + private readonly IObservable _source; + private readonly Func> _collectionSelector; + private readonly Func _resultSelector; + + public TaskSelectorIndexed(IObservable source, Func> collectionSelector, Func resultSelector) + { + _source = source; + _collectionSelector = collectionSelector; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private readonly object _gate = new(); + private readonly CancellationTokenSource _cancel = new(); + + private readonly Func> _collectionSelector; + private readonly Func _resultSelector; + + public _(TaskSelectorIndexed parent, IObserver observer) + : base(observer) + { + _collectionSelector = parent._collectionSelector; + _resultSelector = parent._resultSelector; + } + + private volatile int _count; + private int _index; + + public override void Run(IObservable source) + { + _count = 1; + + base.Run(source); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _cancel.Cancel(); + } + base.Dispose(disposing); + } + + public override void OnNext(TSource value) + { + int index; + + Task task; + try + { + index = checked(_index++); + Interlocked.Increment(ref _count); + task = _collectionSelector(value, index, _cancel.Token); + } + catch (Exception ex) + { + lock (_gate) + { + ForwardOnError(ex); + } + + return; + } + + if (task.IsCompleted) + { + OnCompletedTask(value, index, task); + } + else + { + task.ContinueWithState(static (t, tuple) => tuple.@this.OnCompletedTask(tuple.value, tuple.index, t), (@this: this, value, index), _cancel.Token); + } + } + + private void OnCompletedTask(TSource value, int index, Task task) + { + switch (task.Status) + { + case TaskStatus.RanToCompletion: + { + TResult res; + try + { + res = _resultSelector(value, index, task.Result); + } + catch (Exception ex) + { + lock (_gate) + { + ForwardOnError(ex); + } + + return; + } + + lock (_gate) + { + ForwardOnNext(res); + } + + OnCompleted(); + + break; + } + case TaskStatus.Faulted: + { + lock (_gate) + { + ForwardOnError(TaskHelpers.GetSingleException(task)); + } + + break; + } + case TaskStatus.Canceled: + { + if (!_cancel.IsCancellationRequested) + { + lock (_gate) + { + ForwardOnError(new TaskCanceledException(task)); + } + } + + break; + } + } + } + + public override void OnError(Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + if (Interlocked.Decrement(ref _count) == 0) + { + lock (_gate) + { + ForwardOnCompleted(); + } + } + } + } + } + } + + internal static class SelectMany + { + internal class ObservableSelector : Producer + { + protected readonly IObservable _source; + protected readonly Func> _selector; + + public ObservableSelector(IObservable source, Func> selector) + { + _source = source; + _selector = selector; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal class _ : Sink + { + protected readonly object _gate = new(); + private readonly Func> _selector; + private readonly CompositeDisposable _group = new(); + + private volatile bool _isStopped; + + public _(ObservableSelector parent, IObserver observer) + : base(observer) + { + _selector = parent._selector; + } + + public override void OnNext(TSource value) + { + IObservable inner; + + try + { + inner = _selector(value); + } + catch (Exception ex) + { + lock (_gate) + { + ForwardOnError(ex); + } + return; + } + + SubscribeInner(inner); + } + + public override void OnError(Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + Final(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _group.Dispose(); + } + } + + protected void Final() + { + _isStopped = true; + if (_group.Count == 0) + { + // + // Notice there can be a race between OnCompleted of the source and any + // of the inner sequences, where both see _group.Count == 0, and one is + // waiting for the lock. There won't be a double OnCompleted observation + // though, because the call to Dispose silences the observer by swapping + // in a NopObserver. + // + lock (_gate) + { + ForwardOnCompleted(); + } + } + else + { + DisposeUpstream(); + } + } + + protected void SubscribeInner(IObservable inner) + { + var innerObserver = new InnerObserver(this); + + _group.Add(innerObserver); + innerObserver.SetResource(inner.SubscribeSafe(innerObserver)); + } + + private sealed class InnerObserver : SafeObserver + { + private readonly _ _parent; + + public InnerObserver(_ parent) + { + _parent = parent; + } + + public override void OnNext(TResult value) + { + lock (_parent._gate) + { + _parent.ForwardOnNext(value); + } + } + + public override void OnError(Exception error) + { + lock (_parent._gate) + { + _parent.ForwardOnError(error); + } + } + + public override void OnCompleted() + { + _parent._group.Remove(this); + if (_parent._isStopped && _parent._group.Count == 0) + { + // + // Notice there can be a race between OnCompleted of the source and any + // of the inner sequences, where both see _group.Count == 1, and one is + // waiting for the lock. There won't be a double OnCompleted observation + // though, because the call to Dispose silences the observer by swapping + // in a NopObserver. + // + lock (_parent._gate) + { + _parent.ForwardOnCompleted(); + } + } + } + } + } + } + + internal sealed class ObservableSelectors : ObservableSelector + { + private readonly Func> _selectorOnError; + private readonly Func> _selectorOnCompleted; + + public ObservableSelectors(IObservable source, Func> selector, Func> selectorOnError, Func> selectorOnCompleted) + : base(source, selector) + { + _selectorOnError = selectorOnError; + _selectorOnCompleted = selectorOnCompleted; + } + + protected override ObservableSelector._ CreateSink(IObserver observer) => new _(this, observer); + + internal new sealed class _ : ObservableSelector._ + { + private readonly Func> _selectorOnError; + private readonly Func> _selectorOnCompleted; + + public _(ObservableSelectors parent, IObserver observer) + : base(parent, observer) + { + _selectorOnError = parent._selectorOnError; + _selectorOnCompleted = parent._selectorOnCompleted; + } + + public override void OnError(Exception error) + { + if (_selectorOnError != null) + { + IObservable inner; + + try + { + inner = _selectorOnError(error); + } + catch (Exception ex) + { + lock (_gate) + { + ForwardOnError(ex); + } + return; + } + + SubscribeInner(inner); + + Final(); + } + else + { + base.OnError(error); + } + } + + public override void OnCompleted() + { + if (_selectorOnCompleted != null) + { + IObservable inner; + + try + { + inner = _selectorOnCompleted(); + } + catch (Exception ex) + { + lock (_gate) + { + ForwardOnError(ex); + } + return; + } + + SubscribeInner(inner); + } + + Final(); + } + } + } + + internal class ObservableSelectorIndexed : Producer + { + protected readonly IObservable _source; + protected readonly Func> _selector; + + public ObservableSelectorIndexed(IObservable source, Func> selector) + { + _source = source; + _selector = selector; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal class _ : Sink + { + private readonly object _gate = new(); + private readonly CompositeDisposable _group = new(); + + protected readonly Func> _selector; + + private int _index; + private volatile bool _isStopped; + + public _(ObservableSelectorIndexed parent, IObserver observer) + : base(observer) + { + _selector = parent._selector; + } + + public override void OnNext(TSource value) + { + IObservable inner; + + try + { + inner = _selector(value, checked(_index++)); + } + catch (Exception ex) + { + lock (_gate) + { + ForwardOnError(ex); + } + return; + } + + SubscribeInner(inner); + } + + public override void OnError(Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + Final(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _group.Dispose(); + } + } + + protected void Final() + { + _isStopped = true; + if (_group.Count == 0) + { + // + // Notice there can be a race between OnCompleted of the source and any + // of the inner sequences, where both see _group.Count == 1, and one is + // waiting for the lock. There won't be a double OnCompleted observation + // though, because the call to Dispose silences the observer by swapping + // in a NopObserver. + // + lock (_gate) + { + ForwardOnCompleted(); + } + } + else + { + DisposeUpstream(); + } + } + + protected void SubscribeInner(IObservable inner) + { + var innerObserver = new InnerObserver(this); + + _group.Add(innerObserver); + innerObserver.SetResource(inner.SubscribeSafe(innerObserver)); + } + + private sealed class InnerObserver : SafeObserver + { + private readonly _ _parent; + + public InnerObserver(_ parent) + { + _parent = parent; + } + + public override void OnNext(TResult value) + { + lock (_parent._gate) + { + _parent.ForwardOnNext(value); + } + } + + public override void OnError(Exception error) + { + lock (_parent._gate) + { + _parent.ForwardOnError(error); + } + } + + public override void OnCompleted() + { + _parent._group.Remove(this); + if (_parent._isStopped && _parent._group.Count == 0) + { + // + // Notice there can be a race between OnCompleted of the source and any + // of the inner sequences, where both see _group.Count == 1, and one is + // waiting for the lock. There won't be a double OnCompleted observation + // though, because the call to Dispose silences the observer by swapping + // in a NopObserver. + // + lock (_parent._gate) + { + _parent.ForwardOnCompleted(); + } + } + } + } + } + } + + internal sealed class ObservableSelectorsIndexed : ObservableSelectorIndexed + { + private readonly Func> _selectorOnError; + private readonly Func> _selectorOnCompleted; + + public ObservableSelectorsIndexed(IObservable source, Func> selector, Func> selectorOnError, Func> selectorOnCompleted) + : base(source, selector) + { + _selectorOnError = selectorOnError; + _selectorOnCompleted = selectorOnCompleted; + } + + protected override ObservableSelectorIndexed._ CreateSink(IObserver observer) => new _(this, observer); + + internal new sealed class _ : ObservableSelectorIndexed._ + { + private readonly object _gate = new(); + + private readonly Func> _selectorOnError; + private readonly Func> _selectorOnCompleted; + + public _(ObservableSelectorsIndexed parent, IObserver observer) + : base(parent, observer) + { + _selectorOnError = parent._selectorOnError; + _selectorOnCompleted = parent._selectorOnCompleted; + } + + public override void OnError(Exception error) + { + if (_selectorOnError != null) + { + IObservable inner; + + try + { + inner = _selectorOnError(error); + } + catch (Exception ex) + { + lock (_gate) + { + ForwardOnError(ex); + } + return; + } + + SubscribeInner(inner); + + Final(); + } + else + { + base.OnError(error); + } + } + + public override void OnCompleted() + { + if (_selectorOnCompleted != null) + { + IObservable inner; + + try + { + inner = _selectorOnCompleted(); + } + catch (Exception ex) + { + lock (_gate) + { + ForwardOnError(ex); + } + return; + } + + SubscribeInner(inner); + } + + Final(); + } + } + } + + internal sealed class EnumerableSelector : Producer + { + private readonly IObservable _source; + private readonly Func> _selector; + + public EnumerableSelector(IObservable source, Func> selector) + { + _source = source; + _selector = selector; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private readonly Func> _selector; + + public _(EnumerableSelector parent, IObserver observer) + : base(observer) + { + _selector = parent._selector; + } + + public override void OnNext(TSource value) + { + IEnumerable xs; + try + { + xs = _selector(value); + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + IEnumerator e; + try + { + e = xs.GetEnumerator(); + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + using (e) + { + var hasNext = true; + + while (hasNext) + { + var current = default(TResult); + + try + { + hasNext = e.MoveNext(); + if (hasNext) + { + current = e.Current; + } + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + if (hasNext) + { + ForwardOnNext(current!); + } + } + } + } + } + } + + internal sealed class EnumerableSelectorIndexed : Producer + { + private readonly IObservable _source; + private readonly Func> _selector; + + public EnumerableSelectorIndexed(IObservable source, Func> selector) + { + _source = source; + _selector = selector; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private readonly Func> _selector; + + public _(EnumerableSelectorIndexed parent, IObserver observer) + : base(observer) + { + _selector = parent._selector; + } + + private int _index; + + public override void OnNext(TSource value) + { + IEnumerable xs; + try + { + xs = _selector(value, checked(_index++)); + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + IEnumerator e; + try + { + e = xs.GetEnumerator(); + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + using (e) + { + var hasNext = true; + + while (hasNext) + { + var current = default(TResult); + + try + { + hasNext = e.MoveNext(); + if (hasNext) + { + current = e.Current; + } + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + if (hasNext) + { + ForwardOnNext(current!); + } + } + } + } + } + } + + internal sealed class TaskSelector : Producer + { + private readonly IObservable _source; + private readonly Func> _selector; + + public TaskSelector(IObservable source, Func> selector) + { + _source = source; + _selector = selector; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private readonly object _gate = new(); + private readonly CancellationTokenSource _cts = new(); + + private readonly Func> _selector; + + public _(TaskSelector parent, IObserver observer) + : base(observer) + { + _selector = parent._selector; + } + + private volatile int _count; + + public override void Run(IObservable source) + { + _count = 1; + + base.Run(source); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _cts.Cancel(); + } + base.Dispose(disposing); + } + + public override void OnNext(TSource value) + { + Task task; + try + { + Interlocked.Increment(ref _count); + task = _selector(value, _cts.Token); + } + catch (Exception ex) + { + lock (_gate) + { + ForwardOnError(ex); + } + + return; + } + + if (task.IsCompleted) + { + OnCompletedTask(task); + } + else + { + task.ContinueWith((closureTask, thisObject) => ((_)thisObject!).OnCompletedTask(closureTask), this, _cts.Token); + } + } + + private void OnCompletedTask(Task task) + { + switch (task.Status) + { + case TaskStatus.RanToCompletion: + { + lock (_gate) + { + ForwardOnNext(task.Result); + } + + OnCompleted(); + + break; + } + case TaskStatus.Faulted: + { + lock (_gate) + { + ForwardOnError(TaskHelpers.GetSingleException(task)); + } + + break; + } + case TaskStatus.Canceled: + { + if (!_cts.IsCancellationRequested) + { + lock (_gate) + { + ForwardOnError(new TaskCanceledException(task)); + } + } + + break; + } + } + } + + public override void OnError(Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + if (Interlocked.Decrement(ref _count) == 0) + { + lock (_gate) + { + ForwardOnCompleted(); + } + } + } + } + } + + internal sealed class TaskSelectorIndexed : Producer + { + private readonly IObservable _source; + private readonly Func> _selector; + + public TaskSelectorIndexed(IObservable source, Func> selector) + { + _source = source; + _selector = selector; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private readonly object _gate = new(); + private readonly CancellationTokenSource _cts = new(); + + private readonly Func> _selector; + + public _(TaskSelectorIndexed parent, IObserver observer) + : base(observer) + { + _selector = parent._selector; + } + + private volatile int _count; + private int _index; + + public override void Run(IObservable source) + { + _count = 1; + + base.Run(source); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _cts.Cancel(); + } + base.Dispose(disposing); + } + + public override void OnNext(TSource value) + { + Task task; + try + { + Interlocked.Increment(ref _count); + task = _selector(value, checked(_index++), _cts.Token); + } + catch (Exception ex) + { + lock (_gate) + { + ForwardOnError(ex); + } + + return; + } + + if (task.IsCompleted) + { + OnCompletedTask(task); + } + else + { + task.ContinueWith((closureTask, thisObject) => ((_)thisObject!).OnCompletedTask(closureTask), this, _cts.Token); + } + } + + private void OnCompletedTask(Task task) + { + switch (task.Status) + { + case TaskStatus.RanToCompletion: + { + lock (_gate) + { + ForwardOnNext(task.Result); + } + + OnCompleted(); + + break; + } + case TaskStatus.Faulted: + { + lock (_gate) + { + ForwardOnError(TaskHelpers.GetSingleException(task)); + } + + break; + } + case TaskStatus.Canceled: + { + if (!_cts.IsCancellationRequested) + { + lock (_gate) + { + ForwardOnError(new TaskCanceledException(task)); + } + } + + break; + } + } + } + + public override void OnError(Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + if (Interlocked.Decrement(ref _count) == 0) + { + lock (_gate) + { + ForwardOnCompleted(); + } + } + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/SequenceEqual.cs b/LibExternal/System.Reactive/Linq/Observable/SequenceEqual.cs new file mode 100644 index 0000000..bba056f --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/SequenceEqual.cs @@ -0,0 +1,336 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Disposables; +using System.Threading; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class SequenceEqual + { + internal sealed class Observable : Producer + { + private readonly IObservable _first; + private readonly IObservable _second; + private readonly IEqualityComparer _comparer; + + public Observable(IObservable first, IObservable second, IEqualityComparer comparer) + { + _first = first; + _second = second; + _comparer = comparer; + } + + protected override _ CreateSink(IObserver observer) => new(_comparer, observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : IdentitySink + { + private readonly IEqualityComparer _comparer; + private readonly object _gate; + private readonly Queue _ql; + private readonly Queue _qr; + + public _(IEqualityComparer comparer, IObserver observer) + : base(observer) + { + _comparer = comparer; + _gate = new object(); + _ql = new Queue(); + _qr = new Queue(); + } + + private bool _donel; + private bool _doner; + + private SingleAssignmentDisposableValue _second; + + public void Run(Observable parent) + { + SetUpstream(parent._first.SubscribeSafe(new FirstObserver(this))); + _second.Disposable = parent._second.SubscribeSafe(new SecondObserver(this)); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _second.Dispose(); + } + + base.Dispose(disposing); + } + + private sealed class FirstObserver : IObserver + { + private readonly _ _parent; + + public FirstObserver(_ parent) + { + _parent = parent; + } + + public void OnNext(TSource value) + { + lock (_parent._gate) + { + if (_parent._qr.Count > 0) + { + var equal = false; + var v = _parent._qr.Dequeue(); + try + { + equal = _parent._comparer.Equals(value, v); + } + catch (Exception exception) + { + _parent.ForwardOnError(exception); + return; + } + if (!equal) + { + _parent.ForwardOnNext(false); + _parent.ForwardOnCompleted(); + } + } + else if (_parent._doner) + { + _parent.ForwardOnNext(false); + _parent.ForwardOnCompleted(); + } + else + { + _parent._ql.Enqueue(value); + } + } + } + + public void OnError(Exception error) + { + lock (_parent._gate) + { + _parent.ForwardOnError(error); + } + } + + public void OnCompleted() + { + lock (_parent._gate) + { + _parent._donel = true; + if (_parent._ql.Count == 0) + { + if (_parent._qr.Count > 0) + { + _parent.ForwardOnNext(false); + _parent.ForwardOnCompleted(); + } + else if (_parent._doner) + { + _parent.ForwardOnNext(true); + _parent.ForwardOnCompleted(); + } + } + } + } + } + + private sealed class SecondObserver : IObserver + { + private readonly _ _parent; + + public SecondObserver(_ parent) + { + _parent = parent; + } + + public void OnNext(TSource value) + { + lock (_parent._gate) + { + if (_parent._ql.Count > 0) + { + var equal = false; + var v = _parent._ql.Dequeue(); + try + { + equal = _parent._comparer.Equals(v, value); + } + catch (Exception exception) + { + _parent.ForwardOnError(exception); + return; + } + if (!equal) + { + _parent.ForwardOnNext(false); + _parent.ForwardOnCompleted(); + } + } + else if (_parent._donel) + { + _parent.ForwardOnNext(false); + _parent.ForwardOnCompleted(); + } + else + { + _parent._qr.Enqueue(value); + } + } + } + + public void OnError(Exception error) + { + lock (_parent._gate) + { + _parent.ForwardOnError(error); + } + } + + public void OnCompleted() + { + lock (_parent._gate) + { + _parent._doner = true; + if (_parent._qr.Count == 0) + { + if (_parent._ql.Count > 0) + { + _parent.ForwardOnNext(false); + _parent.ForwardOnCompleted(); + } + else if (_parent._donel) + { + _parent.ForwardOnNext(true); + _parent.ForwardOnCompleted(); + } + } + } + } + } + } + } + + internal sealed class Enumerable : Producer + { + private readonly IObservable _first; + private readonly IEnumerable _second; + private readonly IEqualityComparer _comparer; + + public Enumerable(IObservable first, IEnumerable second, IEqualityComparer comparer) + { + _first = first; + _second = second; + _comparer = comparer; + } + + protected override _ CreateSink(IObserver observer) => new(_comparer, observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : Sink + { + private readonly IEqualityComparer _comparer; + + public _(IEqualityComparer comparer, IObserver observer) + : base(observer) + { + _comparer = comparer; + } + + private IEnumerator? _enumerator; + + private static readonly IEnumerator DisposedEnumerator = MakeDisposedEnumerator(); + + private static IEnumerator MakeDisposedEnumerator() + { + yield break; + } + + public void Run(Enumerable parent) + { + // + // Notice the evaluation order of obtaining the enumerator and subscribing to the + // observable sequence is reversed compared to the operator's signature. This is + // required to make sure the enumerator is available as soon as the observer can + // be called. Otherwise, we end up having a race for the initialization and use + // of the _rightEnumerator field. + // + try + { + var enumerator = parent._second.GetEnumerator(); + + if (Interlocked.CompareExchange(ref _enumerator, enumerator, null) != null) + { + enumerator.Dispose(); + return; + } + } + catch (Exception exception) + { + ForwardOnError(exception); + + return; + } + + SetUpstream(parent._first.SubscribeSafe(this)); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + Interlocked.Exchange(ref _enumerator, DisposedEnumerator)?.Dispose(); + } + + base.Dispose(disposing); + } + + public override void OnNext(TSource value) + { + var equal = false; + + try + { + if (_enumerator!.MoveNext()) // NB: Non-null after Run is called. + { + var current = _enumerator.Current; + equal = _comparer.Equals(value, current); + } + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + if (!equal) + { + ForwardOnNext(false); + ForwardOnCompleted(); + } + } + + public override void OnCompleted() + { + bool hasNext; + try + { + hasNext = _enumerator!.MoveNext(); // NB: Non-null after Run is called. + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + ForwardOnNext(!hasNext); + ForwardOnCompleted(); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/SingleAsync.cs b/LibExternal/System.Reactive/Linq/Observable/SingleAsync.cs new file mode 100644 index 0000000..251594f --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/SingleAsync.cs @@ -0,0 +1,156 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class SingleAsync + { + internal sealed class Sequence : Producer + { + private readonly IObservable _source; + + public Sequence(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private TSource? _value; + private bool _seenValue; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(TSource value) + { + if (_seenValue) + { + try + { + throw new InvalidOperationException(Strings_Linq.MORE_THAN_ONE_ELEMENT); + } + catch (Exception e) + { + ForwardOnError(e); + } + return; + } + + _value = value; + _seenValue = true; + } + + public override void OnCompleted() + { + if (!_seenValue) + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + else + { + ForwardOnNext(_value!); + ForwardOnCompleted(); + } + } + } + } + + internal sealed class Predicate : Producer + { + private readonly IObservable _source; + private readonly Func _predicate; + + public Predicate(IObservable source, Func predicate) + { + _source = source; + _predicate = predicate; + } + + protected override _ CreateSink(IObserver observer) => new(_predicate, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly Func _predicate; + private TSource? _value; + private bool _seenValue; + + public _(Func predicate, IObserver observer) + : base(observer) + { + _predicate = predicate; + } + + public override void OnNext(TSource value) + { + var b = false; + + try + { + b = _predicate(value); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + if (b) + { + if (_seenValue) + { + try + { + throw new InvalidOperationException(Strings_Linq.MORE_THAN_ONE_MATCHING_ELEMENT); + } + catch (Exception e) + { + ForwardOnError(e); + } + return; + } + + _value = value; + _seenValue = true; + } + } + + public override void OnCompleted() + { + if (!_seenValue) + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_MATCHING_ELEMENTS); + } + catch (Exception e) + { + ForwardOnError(e); + } + } + else + { + ForwardOnNext(_value!); + ForwardOnCompleted(); + } + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/SingleOrDefaultAsync.cs b/LibExternal/System.Reactive/Linq/Observable/SingleOrDefaultAsync.cs new file mode 100644 index 0000000..0260907 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/SingleOrDefaultAsync.cs @@ -0,0 +1,128 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class SingleOrDefaultAsync + { + internal sealed class Sequence : Producer + { + private readonly IObservable _source; + + public Sequence(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private TSource? _value; + private bool _seenValue; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(TSource value) + { + if (_seenValue) + { + try + { + throw new InvalidOperationException(Strings_Linq.MORE_THAN_ONE_ELEMENT); + } + catch (Exception e) + { + ForwardOnError(e); + } + return; + } + + _value = value; + _seenValue = true; + } + + public override void OnCompleted() + { + ForwardOnNext(_value); + ForwardOnCompleted(); + } + } + } + + internal sealed class Predicate : Producer + { + private readonly IObservable _source; + private readonly Func _predicate; + + public Predicate(IObservable source, Func predicate) + { + _source = source; + _predicate = predicate; + } + + protected override _ CreateSink(IObserver observer) => new(_predicate, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private readonly Func _predicate; + private TSource? _value; + private bool _seenValue; + + public _(Func predicate, IObserver observer) + : base(observer) + { + _predicate = predicate; + } + + public override void OnNext(TSource value) + { + var b = false; + + try + { + b = _predicate(value); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + if (b) + { + if (_seenValue) + { + try + { + throw new InvalidOperationException(Strings_Linq.MORE_THAN_ONE_MATCHING_ELEMENT); + } + catch (Exception e) + { + ForwardOnError(e); + } + return; + } + + _value = value; + _seenValue = true; + } + } + + public override void OnCompleted() + { + ForwardOnNext(_value); + ForwardOnCompleted(); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Skip.cs b/LibExternal/System.Reactive/Linq/Observable/Skip.cs new file mode 100644 index 0000000..8c51ad6 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Skip.cs @@ -0,0 +1,141 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class Skip + { + internal sealed class Count : Producer + { + private readonly IObservable _source; + private readonly int _count; + + public Count(IObservable source, int count) + { + _source = source; + _count = count; + } + + public IObservable Combine(int count) + { + // + // Sum semantics: + // + // xs --o--o--o--o--o--o--| xs --o--o--o--o--o--o--| + // xs.Skip(2) --x--x--o--o--o--o--| xs.Skip(3) --x--x--x--o--o--o--| + // xs.Skip(2).Skip(3) --------x--x--x--o--| xs.Skip(3).Skip(2) -----------x--x--o--| + // + return new Count(_source, _count + count); + } + + protected override _ CreateSink(IObserver observer) => new(_count, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private int _remaining; + + public _(int count, IObserver observer) + : base(observer) + { + _remaining = count; + } + + public override void OnNext(TSource value) + { + if (_remaining <= 0) + { + ForwardOnNext(value); + } + else + { + _remaining--; + } + } + } + } + + internal sealed class Time : Producer + { + private readonly IObservable _source; + private readonly TimeSpan _duration; + internal readonly IScheduler _scheduler; + + public Time(IObservable source, TimeSpan duration, IScheduler scheduler) + { + _source = source; + _duration = duration; + _scheduler = scheduler; + } + + public IObservable Combine(TimeSpan duration) + { + // + // Maximum semantics: + // + // t 0--1--2--3--4--5--6--7-> t 0--1--2--3--4--5--6--7-> + // + // xs --o--o--o--o--o--o--| xs --o--o--o--o--o--o--| + // xs.Skip(2s) xxxxxxx-o--o--o--o--| xs.Skip(3s) xxxxxxxxxx-o--o--o--| + // xs.Skip(2s).Skip(3s) xxxxxxxxxx-o--o--o--| xs.Skip(3s).Skip(2s) xxxxxxx----o--o--o--| + // + if (duration <= _duration) + { + return this; + } + + return new Time(_source, duration, _scheduler); + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : IdentitySink + { + private volatile bool _open; + + public _(IObserver observer) + : base(observer) + { + } + + private SingleAssignmentDisposableValue _sourceDisposable; + + public void Run(Time parent) + { + SetUpstream(parent._scheduler.ScheduleAction(this, parent._duration, state => state.Tick())); + _sourceDisposable.Disposable = parent._source.SubscribeSafe(this); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _sourceDisposable.Dispose(); + } + + base.Dispose(disposing); + } + + private void Tick() + { + _open = true; + } + + public override void OnNext(TSource value) + { + if (_open) + { + ForwardOnNext(value); + } + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/SkipLast.cs b/LibExternal/System.Reactive/Linq/Observable/SkipLast.cs new file mode 100644 index 0000000..96a15d4 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/SkipLast.cs @@ -0,0 +1,115 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Concurrency; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class SkipLast + { + internal sealed class Count : Producer + { + private readonly IObservable _source; + private readonly int _count; + + public Count(IObservable source, int count) + { + _source = source; + _count = count; + } + + protected override _ CreateSink(IObserver observer) => new(_count, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly int _count; + private readonly Queue _queue; + + public _(int count, IObserver observer) + : base(observer) + { + _count = count; + _queue = new Queue(); + } + + public override void OnNext(TSource value) + { + _queue.Enqueue(value); + + if (_queue.Count > _count) + { + ForwardOnNext(_queue.Dequeue()); + } + } + } + } + + internal sealed class Time : Producer + { + private readonly IObservable _source; + private readonly TimeSpan _duration; + private readonly IScheduler _scheduler; + + public Time(IObservable source, TimeSpan duration, IScheduler scheduler) + { + _source = source; + _duration = duration; + _scheduler = scheduler; + } + + protected override _ CreateSink(IObserver observer) => new(_duration, observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : IdentitySink + { + private readonly TimeSpan _duration; + private readonly Queue> _queue; + + public _(TimeSpan duration, IObserver observer) + : base(observer) + { + _duration = duration; + _queue = new Queue>(); + } + + private IStopwatch? _watch; + + public void Run(Time parent) + { + _watch = parent._scheduler.StartStopwatch(); + + SetUpstream(parent._source.SubscribeSafe(this)); + } + + public override void OnNext(TSource value) + { + var now = _watch!.Elapsed; + + _queue.Enqueue(new Reactive.TimeInterval(value, now)); + + while (_queue.Count > 0 && now - _queue.Peek().Interval >= _duration) + { + ForwardOnNext(_queue.Dequeue().Value); + } + } + + public override void OnCompleted() + { + var now = _watch!.Elapsed; + + while (_queue.Count > 0 && now - _queue.Peek().Interval >= _duration) + { + ForwardOnNext(_queue.Dequeue().Value); + } + + ForwardOnCompleted(); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/SkipUntil.cs b/LibExternal/System.Reactive/Linq/Observable/SkipUntil.cs new file mode 100644 index 0000000..9f33dbb --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/SkipUntil.cs @@ -0,0 +1,199 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class SkipUntil : Producer._> + { + private readonly IObservable _source; + private readonly IObservable _other; + + public SkipUntil(IObservable source, IObservable other) + { + _source = source; + _other = other; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : IdentitySink + { + private SingleAssignmentDisposableValue _otherDisposable; + private bool _forward; + private int _halfSerializer; + private Exception? _error; + + public _(IObserver observer) + : base(observer) + { + } + + public void Run(SkipUntil parent) + { + _otherDisposable.Disposable = parent._other.Subscribe(new OtherObserver(this)); + Run(parent._source); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (!_otherDisposable.IsDisposed) + { + _otherDisposable.Dispose(); + } + } + + base.Dispose(disposing); + } + + public override void OnNext(TSource value) + { + if (_forward) + { + HalfSerializer.ForwardOnNext(this, value, ref _halfSerializer, ref _error); + } + } + + public override void OnError(Exception ex) + { + HalfSerializer.ForwardOnError(this, ex, ref _halfSerializer, ref _error); + } + + public override void OnCompleted() + { + if (_forward) + { + HalfSerializer.ForwardOnCompleted(this, ref _halfSerializer, ref _error); + } + else + { + DisposeUpstream(); + } + } + + private void OtherComplete() + { + _forward = true; + } + + private sealed class OtherObserver : IObserver, IDisposable + { + private readonly _ _parent; + + public OtherObserver(_ parent) + { + _parent = parent; + } + + public void Dispose() + { + if (!_parent._otherDisposable.IsDisposed) + { + _parent._otherDisposable.Dispose(); + } + } + + public void OnCompleted() + { + Dispose(); + } + + public void OnError(Exception error) + { + HalfSerializer.ForwardOnError(_parent, error, ref _parent._halfSerializer, ref _parent._error); + } + + public void OnNext(TOther value) + { + _parent.OtherComplete(); + Dispose(); + } + } + } + } + + internal sealed class SkipUntil : Producer._> + { + private readonly IObservable _source; + private readonly DateTimeOffset _startTime; + internal readonly IScheduler _scheduler; + + public SkipUntil(IObservable source, DateTimeOffset startTime, IScheduler scheduler) + { + _source = source; + _startTime = startTime; + _scheduler = scheduler; + } + + public IObservable Combine(DateTimeOffset startTime) + { + // + // Maximum semantics: + // + // t 0--1--2--3--4--5--6--7-> t 0--1--2--3--4--5--6--7-> + // + // xs --o--o--o--o--o--o--| xs --o--o--o--o--o--o--| + // xs.SU(5AM) xxxxxxxxxxxxxxxx-o--| xs.SU(3AM) xxxxxxxxxx-o--o--o--| + // xs.SU(5AM).SU(3AM) xxxxxxxxx--------o--| xs.SU(3AM).SU(5AM) xxxxxxxxxxxxxxxx-o--| + // + if (startTime <= _startTime) + { + return this; + } + + return new SkipUntil(_source, startTime, _scheduler); + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : IdentitySink + { + private bool _open; + + public _(IObserver observer) + : base(observer) + { + } + + private SingleAssignmentDisposableValue _task; + + public void Run(SkipUntil parent) + { + _task.Disposable = parent._scheduler.ScheduleAction(this, parent._startTime, static state => state.Tick()); + Run(parent._source); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _task.Dispose(); + } + + base.Dispose(disposing); + } + + private void Tick() + { + _open = true; + } + + public override void OnNext(TSource value) + { + if (_open) + { + ForwardOnNext(value); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/SkipWhile.cs b/LibExternal/System.Reactive/Linq/Observable/SkipWhile.cs new file mode 100644 index 0000000..4ae7a09 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/SkipWhile.cs @@ -0,0 +1,120 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class SkipWhile + { + internal sealed class Predicate : Producer + { + private readonly IObservable _source; + private readonly Func _predicate; + + public Predicate(IObservable source, Func predicate) + { + _source = source; + _predicate = predicate; + } + + protected override _ CreateSink(IObserver observer) => new(_predicate, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private Func? _predicate; + + public _(Func predicate, IObserver observer) + : base(observer) + { + _predicate = predicate; + } + + public override void OnNext(TSource value) + { + if (_predicate != null) + { + bool shouldStart; + try + { + shouldStart = !_predicate(value); + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + if (shouldStart) + { + _predicate = null; + + ForwardOnNext(value); + } + } + else + { + ForwardOnNext(value); + } + } + } + } + + internal sealed class PredicateIndexed : Producer + { + private readonly IObservable _source; + private readonly Func _predicate; + + public PredicateIndexed(IObservable source, Func predicate) + { + _source = source; + _predicate = predicate; + } + + protected override _ CreateSink(IObserver observer) => new(_predicate, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private Func? _predicate; + private int _index; + + public _(Func predicate, IObserver observer) + : base(observer) + { + _predicate = predicate; + } + + public override void OnNext(TSource value) + { + if (_predicate != null) + { + bool shouldStart; + try + { + shouldStart = !_predicate(value, checked(_index++)); + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + if (shouldStart) + { + _predicate = null; + + ForwardOnNext(value); + } + } + else + { + ForwardOnNext(value); + } + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Sum.cs b/LibExternal/System.Reactive/Linq/Observable/Sum.cs new file mode 100644 index 0000000..22e9074 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Sum.cs @@ -0,0 +1,411 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class SumDouble : Producer + { + private readonly IObservable _source; + + public SumDouble(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private double _sum; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(double value) + { + _sum += value; + } + + public override void OnCompleted() + { + ForwardOnNext(_sum); + ForwardOnCompleted(); + } + } + } + + internal sealed class SumSingle : Producer + { + private readonly IObservable _source; + + public SumSingle(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private double _sum; // This is what LINQ to Objects does (accumulates into double that is)! + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(float value) + { + _sum += value; // This is what LINQ to Objects does! + } + + public override void OnCompleted() + { + ForwardOnNext((float)_sum); // This is what LINQ to Objects does! + ForwardOnCompleted(); + } + } + } + + internal sealed class SumDecimal : Producer + { + private readonly IObservable _source; + + public SumDecimal(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private decimal _sum; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(decimal value) + { + _sum += value; + } + + public override void OnCompleted() + { + ForwardOnNext(_sum); + ForwardOnCompleted(); + } + } + } + + internal sealed class SumInt32 : Producer + { + private readonly IObservable _source; + + public SumInt32(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private int _sum; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(int value) + { + try + { + checked + { + _sum += value; + } + } + catch (Exception exception) + { + ForwardOnError(exception); + } + } + + public override void OnCompleted() + { + ForwardOnNext(_sum); + ForwardOnCompleted(); + } + } + } + + internal sealed class SumInt64 : Producer + { + private readonly IObservable _source; + + public SumInt64(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private long _sum; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(long value) + { + try + { + checked + { + _sum += value; + } + } + catch (Exception exception) + { + ForwardOnError(exception); + } + } + + public override void OnCompleted() + { + ForwardOnNext(_sum); + ForwardOnCompleted(); + } + } + } + + internal sealed class SumDoubleNullable : Producer + { + private readonly IObservable _source; + + public SumDoubleNullable(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private double _sum; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(double? value) + { + if (value != null) + { + _sum += value.Value; + } + } + + public override void OnCompleted() + { + ForwardOnNext(_sum); + ForwardOnCompleted(); + } + } + } + + internal sealed class SumSingleNullable : Producer + { + private readonly IObservable _source; + + public SumSingleNullable(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private double _sum; // This is what LINQ to Objects does (accumulates into double that is)! + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(float? value) + { + if (value != null) + { + _sum += value.Value; // This is what LINQ to Objects does! + } + } + + public override void OnCompleted() + { + ForwardOnNext((float)_sum); // This is what LINQ to Objects does! + ForwardOnCompleted(); + } + } + } + + internal sealed class SumDecimalNullable : Producer + { + private readonly IObservable _source; + + public SumDecimalNullable(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private decimal _sum; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(decimal? value) + { + if (value != null) + { + _sum += value.Value; + } + } + + public override void OnCompleted() + { + ForwardOnNext(_sum); + ForwardOnCompleted(); + } + } + } + + internal sealed class SumInt32Nullable : Producer + { + private readonly IObservable _source; + + public SumInt32Nullable(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private int _sum; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(int? value) + { + try + { + checked + { + if (value != null) + { + _sum += value.Value; + } + } + } + catch (Exception exception) + { + ForwardOnError(exception); + } + } + + public override void OnCompleted() + { + ForwardOnNext(_sum); + ForwardOnCompleted(); + } + } + } + + internal sealed class SumInt64Nullable : Producer + { + private readonly IObservable _source; + + public SumInt64Nullable(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private long _sum; + + public _(IObserver observer) + : base(observer) + { + } + + public override void OnNext(long? value) + { + try + { + checked + { + if (value != null) + { + _sum += value.Value; + } + } + } + catch (Exception exception) + { + ForwardOnError(exception); + } + } + + public override void OnCompleted() + { + ForwardOnNext(_sum); + ForwardOnCompleted(); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Switch.cs b/LibExternal/System.Reactive/Linq/Observable/Switch.cs new file mode 100644 index 0000000..7359d72 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Switch.cs @@ -0,0 +1,139 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Switch : Producer._> + { + private readonly IObservable> _sources; + + public Switch(IObservable> sources) + { + _sources = sources; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_sources); + + internal sealed class _ : Sink, TSource> + { + private readonly object _gate = new(); + + public _(IObserver observer) + : base(observer) + { + } + + private SerialDisposableValue _innerSerialDisposable; + private bool _isStopped; + private ulong _latest; + private bool _hasLatest; + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _innerSerialDisposable.Dispose(); + } + + base.Dispose(disposing); + } + + public override void OnNext(IObservable value) + { + ulong id; + + lock (_gate) + { + id = unchecked(++_latest); + _hasLatest = true; + } + + var innerObserver = new InnerObserver(this, id); + + _innerSerialDisposable.Disposable = innerObserver; + innerObserver.SetResource(value.SubscribeSafe(innerObserver)); + } + + public override void OnError(Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + lock (_gate) + { + DisposeUpstream(); + + _isStopped = true; + if (!_hasLatest) + { + ForwardOnCompleted(); + } + } + } + + private sealed class InnerObserver : SafeObserver + { + private readonly _ _parent; + private readonly ulong _id; + + public InnerObserver(_ parent, ulong id) + { + _parent = parent; + _id = id; + } + + public override void OnNext(TSource value) + { + lock (_parent._gate) + { + if (_parent._latest == _id) + { + _parent.ForwardOnNext(value); + } + } + } + + public override void OnError(Exception error) + { + lock (_parent._gate) + { + Dispose(); + + if (_parent._latest == _id) + { + _parent.ForwardOnError(error); + } + } + } + + public override void OnCompleted() + { + lock (_parent._gate) + { + Dispose(); + + if (_parent._latest == _id) + { + _parent._hasLatest = false; + + if (_parent._isStopped) + { + _parent.ForwardOnCompleted(); + } + } + } + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Synchronize.cs b/LibExternal/System.Reactive/Linq/Observable/Synchronize.cs new file mode 100644 index 0000000..18cf161 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Synchronize.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Synchronize : Producer._> + { + private readonly IObservable _source; + private readonly object? _gate; + + public Synchronize(IObservable source, object gate) + { + _source = source; + _gate = gate; + } + + public Synchronize(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(_gate, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly object _gate; + + public _(object? gate, IObserver observer) + : base(observer) + { + _gate = gate ?? new object(); + } + + public override void OnNext(TSource value) + { + lock (_gate) + { + ForwardOnNext(value); + } + } + + public override void OnError(Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + lock (_gate) + { + ForwardOnCompleted(); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Take.cs b/LibExternal/System.Reactive/Linq/Observable/Take.cs new file mode 100644 index 0000000..228e19b --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Take.cs @@ -0,0 +1,167 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class Take + { + internal sealed class Count : Producer + { + private readonly IObservable _source; + private readonly int _count; + + public Count(IObservable source, int count) + { + _source = source; + _count = count; + } + + public IObservable Combine(int count) + { + // + // Minimum semantics: + // + // xs --o--o--o--o--o--o--| xs --o--o--o--o--o--o--| + // xs.Take(5) --o--o--o--o--o| xs.Take(3) --o--o--o| + // xs.Take(5).Take(3) --o--o--o| xs.Take(3).Take(5) --o--o--o| + // + if (_count <= count) + { + return this; + } + + return new Count(_source, count); + } + + protected override _ CreateSink(IObserver observer) => new(_count, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private int _remaining; + + public _(int count, IObserver observer) + : base(observer) + { + _remaining = count; + } + + public override void OnNext(TSource value) + { + if (_remaining > 0) + { + --_remaining; + ForwardOnNext(value); + + if (_remaining == 0) + { + ForwardOnCompleted(); + } + } + } + } + } + + internal sealed class Time : Producer + { + private readonly IObservable _source; + private readonly TimeSpan _duration; + internal readonly IScheduler _scheduler; + + public Time(IObservable source, TimeSpan duration, IScheduler scheduler) + { + _source = source; + _duration = duration; + _scheduler = scheduler; + } + + public IObservable Combine(TimeSpan duration) + { + // + // Minimum semantics: + // + // t 0--1--2--3--4--5--6--7-> t 0--1--2--3--4--5--6--7-> + // + // xs --o--o--o--o--o--o--| xs --o--o--o--o--o--o--| + // xs.Take(5s) --o--o--o--o--o| xs.Take(3s) --o--o--o| + // xs.Take(5s).Take(3s) --o--o--o| xs.Take(3s).Take(5s) --o--o--o| + // + if (_duration <= duration) + { + return this; + } + + return new Time(_source, duration, _scheduler); + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : IdentitySink + { + private readonly object _gate = new(); + + public _(IObserver observer) + : base(observer) + { + } + + private SingleAssignmentDisposableValue _task; + + public void Run(Time parent) + { + _task.Disposable = parent._scheduler.ScheduleAction(this, parent._duration, state => state.Tick()); + Run(parent._source); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _task.Dispose(); + } + + base.Dispose(disposing); + } + + private void Tick() + { + lock (_gate) + { + ForwardOnCompleted(); + } + } + + public override void OnNext(TSource value) + { + lock (_gate) + { + ForwardOnNext(value); + } + } + + public override void OnError(Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + lock (_gate) + { + ForwardOnCompleted(); + } + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/TakeLast.cs b/LibExternal/System.Reactive/Linq/Observable/TakeLast.cs new file mode 100644 index 0000000..765158d --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/TakeLast.cs @@ -0,0 +1,245 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Concurrency; +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class TakeLast + { + internal sealed class Count : Producer + { + private readonly IObservable _source; + private readonly int _count; + private readonly IScheduler _loopScheduler; + + public Count(IObservable source, int count, IScheduler loopScheduler) + { + _source = source; + _count = count; + _loopScheduler = loopScheduler; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly int _count; + private readonly IScheduler _loopScheduler; + private readonly Queue _queue; + + public _(Count parent, IObserver observer) + : base(observer) + { + _count = parent._count; + _loopScheduler = parent._loopScheduler; + _queue = new Queue(); + } + + private MultipleAssignmentDisposableValue _loopDisposable; + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _loopDisposable.Dispose(); + } + + base.Dispose(disposing); + } + + public override void OnNext(TSource value) + { + _queue.Enqueue(value); + + if (_queue.Count > _count) + { + _queue.Dequeue(); + } + } + + public override void OnCompleted() + { + DisposeUpstream(); + + var longRunning = _loopScheduler.AsLongRunning(); + if (longRunning != null) + { + _loopDisposable.TrySetFirst(longRunning.ScheduleLongRunning(this, static (@this, c) => @this.Loop(c))); + } + else + { + var first = _loopScheduler.Schedule(this, static (innerScheduler, @this) => @this.LoopRec(innerScheduler)); + _loopDisposable.TrySetFirst(first); + } + } + + private IDisposable LoopRec(IScheduler scheduler) + { + if (_queue.Count > 0) + { + ForwardOnNext(_queue.Dequeue()); + + var next = scheduler.Schedule(this, static (innerScheduler, @this) => @this.LoopRec(innerScheduler)); + _loopDisposable.Disposable = next; + } + else + { + ForwardOnCompleted(); + } + return Disposable.Empty; + } + + private void Loop(ICancelable cancel) + { + var n = _queue.Count; + + while (!cancel.IsDisposed) + { + if (n == 0) + { + ForwardOnCompleted(); + break; + } + + ForwardOnNext(_queue.Dequeue()); + + n--; + } + + Dispose(); + } + } + } + + internal sealed class Time : Producer + { + private readonly IObservable _source; + private readonly TimeSpan _duration; + private readonly IScheduler _scheduler; + private readonly IScheduler _loopScheduler; + + public Time(IObservable source, TimeSpan duration, IScheduler scheduler, IScheduler loopScheduler) + { + _source = source; + _duration = duration; + _scheduler = scheduler; + _loopScheduler = loopScheduler; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source, _scheduler); + + internal sealed class _ : IdentitySink + { + private readonly TimeSpan _duration; + private readonly IScheduler _loopScheduler; + private readonly Queue> _queue; + + public _(Time parent, IObserver observer) + : base(observer) + { + _duration = parent._duration; + _loopScheduler = parent._loopScheduler; + _queue = new Queue>(); + } + + private MultipleAssignmentDisposableValue _loopDisposable; + private IStopwatch? _watch; + + public void Run(IObservable source, IScheduler scheduler) + { + _watch = scheduler.StartStopwatch(); + Run(source); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _loopDisposable.Dispose(); + } + + base.Dispose(disposing); + } + + public override void OnNext(TSource value) + { + var now = _watch!.Elapsed; + _queue.Enqueue(new Reactive.TimeInterval(value, now)); + Trim(now); + } + + public override void OnCompleted() + { + DisposeUpstream(); + + var now = _watch!.Elapsed; + Trim(now); + + var longRunning = _loopScheduler.AsLongRunning(); + if (longRunning != null) + { + _loopDisposable.TrySetFirst(longRunning.ScheduleLongRunning(this, static (@this, c) => @this.Loop(c))); + } + else + { + var first = _loopScheduler.Schedule(this, static (innerScheduler, @this) => @this.LoopRec(innerScheduler)); + _loopDisposable.TrySetFirst(first); + } + } + + private IDisposable LoopRec(IScheduler scheduler) + { + if (_queue.Count > 0) + { + ForwardOnNext(_queue.Dequeue().Value); + + var next = scheduler.Schedule(this, static (innerScheduler, @this) => @this.LoopRec(innerScheduler)); + _loopDisposable.Disposable = next; + } + else + { + ForwardOnCompleted(); + } + + return Disposable.Empty; + } + + private void Loop(ICancelable cancel) + { + var n = _queue.Count; + + while (!cancel.IsDisposed) + { + if (n == 0) + { + ForwardOnCompleted(); + break; + } + + ForwardOnNext(_queue.Dequeue().Value); + + n--; + } + + Dispose(); + } + + private void Trim(TimeSpan now) + { + while (_queue.Count > 0 && now - _queue.Peek().Interval >= _duration) + { + _queue.Dequeue(); + } + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/TakeLastBuffer.cs b/LibExternal/System.Reactive/Linq/Observable/TakeLastBuffer.cs new file mode 100644 index 0000000..dcb65b5 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/TakeLastBuffer.cs @@ -0,0 +1,137 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Concurrency; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class TakeLastBuffer + { + internal sealed class Count : Producer, Count._> + { + private readonly IObservable _source; + private readonly int _count; + + public Count(IObservable source, int count) + { + _source = source; + _count = count; + } + + protected override _ CreateSink(IObserver> observer) => new(_count, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink> + { + private readonly int _count; + private readonly Queue _queue; + + public _(int count, IObserver> observer) + : base(observer) + { + _count = count; + _queue = new Queue(); + } + + public override void OnNext(TSource value) + { + _queue.Enqueue(value); + + if (_queue.Count > _count) + { + _queue.Dequeue(); + } + } + + public override void OnCompleted() + { + var res = new List(_queue.Count); + + while (_queue.Count > 0) + { + res.Add(_queue.Dequeue()); + } + + ForwardOnNext(res); + ForwardOnCompleted(); + } + } + } + + internal sealed class Time : Producer, Time._> + { + private readonly IObservable _source; + private readonly TimeSpan _duration; + private readonly IScheduler _scheduler; + + public Time(IObservable source, TimeSpan duration, IScheduler scheduler) + { + _source = source; + _duration = duration; + _scheduler = scheduler; + } + + protected override _ CreateSink(IObserver> observer) => new(_duration, observer); + + protected override void Run(_ sink) => sink.Run(_source, _scheduler); + + internal sealed class _ : Sink> + { + private readonly TimeSpan _duration; + private readonly Queue> _queue; + + public _(TimeSpan duration, IObserver> observer) + : base(observer) + { + _duration = duration; + _queue = new Queue>(); + } + + private IStopwatch? _watch; + + public void Run(IObservable source, IScheduler scheduler) + { + _watch = scheduler.StartStopwatch(); + + Run(source); + } + + public override void OnNext(TSource value) + { + var now = _watch!.Elapsed; + + _queue.Enqueue(new Reactive.TimeInterval(value, now)); + + Trim(now); + } + + public override void OnCompleted() + { + var now = _watch!.Elapsed; + Trim(now); + + var res = new List(_queue.Count); + + while (_queue.Count > 0) + { + res.Add(_queue.Dequeue().Value); + } + + ForwardOnNext(res); + ForwardOnCompleted(); + } + + private void Trim(TimeSpan now) + { + while (_queue.Count > 0 && now - _queue.Peek().Interval >= _duration) + { + _queue.Dequeue(); + } + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/TakeUntil.cs b/LibExternal/System.Reactive/Linq/Observable/TakeUntil.cs new file mode 100644 index 0000000..3d434a1 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/TakeUntil.cs @@ -0,0 +1,183 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class TakeUntil : Producer._> + { + private readonly IObservable _source; + private readonly IObservable _other; + + public TakeUntil(IObservable source, IObservable other) + { + _source = source; + _other = other; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : IdentitySink + { + private SingleAssignmentDisposableValue _otherDisposable; + private int _halfSerializer; + private Exception? _error; + + public _(IObserver observer) + : base(observer) + { + } + + public void Run(TakeUntil parent) + { + _otherDisposable.Disposable = parent._other.Subscribe(new OtherObserver(this)); + Run(parent._source); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (!_otherDisposable.IsDisposed) + { + _otherDisposable.Dispose(); + } + } + + base.Dispose(disposing); + } + + public override void OnNext(TSource value) + { + HalfSerializer.ForwardOnNext(this, value, ref _halfSerializer, ref _error); + } + + public override void OnError(Exception ex) + { + HalfSerializer.ForwardOnError(this, ex, ref _halfSerializer, ref _error); + } + + public override void OnCompleted() + { + HalfSerializer.ForwardOnCompleted(this, ref _halfSerializer, ref _error); + } + + private sealed class OtherObserver : IObserver + { + private readonly _ _parent; + + public OtherObserver(_ parent) + { + _parent = parent; + } + + public void OnCompleted() + { + // Completion doesn't mean termination in Rx.NET for this operator + _parent._otherDisposable.Dispose(); + } + + public void OnError(Exception error) + { + HalfSerializer.ForwardOnError(_parent, error, ref _parent._halfSerializer, ref _parent._error); + } + + public void OnNext(TOther value) + { + HalfSerializer.ForwardOnCompleted(_parent, ref _parent._halfSerializer, ref _parent._error); + } + } + + } + } + + internal sealed class TakeUntil : Producer._> + { + private readonly IObservable _source; + private readonly DateTimeOffset _endTime; + internal readonly IScheduler _scheduler; + + public TakeUntil(IObservable source, DateTimeOffset endTime, IScheduler scheduler) + { + _source = source; + _endTime = endTime; + _scheduler = scheduler; + } + + public IObservable Combine(DateTimeOffset endTime) + { + // + // Minimum semantics: + // + // t 0--1--2--3--4--5--6--7-> t 0--1--2--3--4--5--6--7-> + // + // xs --o--o--o--o--o--o--| xs --o--o--o--o--o--o--| + // xs.TU(5AM) --o--o--o--o--o| xs.TU(3AM) --o--o--o| + // xs.TU(5AM).TU(3AM) --o--o--o| xs.TU(3AM).TU(5AM) --o--o--o| + // + if (_endTime <= endTime) + { + return this; + } + + return new TakeUntil(_source, endTime, _scheduler); + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : IdentitySink + { + private SingleAssignmentDisposableValue _timerDisposable; + private int _wip; + private Exception? _error; + + public _(IObserver observer) + : base(observer) + { + } + + public void Run(TakeUntil parent) + { + _timerDisposable.Disposable = parent._scheduler.ScheduleAction(this, parent._endTime, state => state.Tick()); + Run(parent._source); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _timerDisposable.Dispose(); + } + + base.Dispose(disposing); + } + + private void Tick() + { + OnCompleted(); + } + + public override void OnNext(TSource value) + { + HalfSerializer.ForwardOnNext(this, value, ref _wip, ref _error); + } + + public override void OnError(Exception error) + { + HalfSerializer.ForwardOnError(this, error, ref _wip, ref _error); + } + + public override void OnCompleted() + { + HalfSerializer.ForwardOnCompleted(this, ref _wip, ref _error); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/TakeUntilPredicate.cs b/LibExternal/System.Reactive/Linq/Observable/TakeUntilPredicate.cs new file mode 100644 index 0000000..422c978 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/TakeUntilPredicate.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + /// + /// Relays items to the downstream until the predicate returns true. + /// + /// The element type of the sequence + internal sealed class TakeUntilPredicate : + Producer.TakeUntilPredicateObserver> + { + private readonly IObservable _source; + private readonly Func _stopPredicate; + + public TakeUntilPredicate(IObservable source, Func stopPredicate) + { + _source = source; + _stopPredicate = stopPredicate; + } + + protected override TakeUntilPredicateObserver CreateSink(IObserver observer) => new(observer, _stopPredicate); + + protected override void Run(TakeUntilPredicateObserver sink) => sink.Run(_source); + + internal sealed class TakeUntilPredicateObserver : IdentitySink + { + private readonly Func _stopPredicate; + + public TakeUntilPredicateObserver(IObserver downstream, Func predicate) + : base(downstream) + { + _stopPredicate = predicate; + } + + public override void OnCompleted() => ForwardOnCompleted(); + + public override void OnError(Exception error) => ForwardOnError(error); + + public override void OnNext(TSource value) + { + ForwardOnNext(value); + + var shouldStop = false; + try + { + shouldStop = _stopPredicate(value); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + if (shouldStop) + { + ForwardOnCompleted(); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/TakeWhile.cs b/LibExternal/System.Reactive/Linq/Observable/TakeWhile.cs new file mode 100644 index 0000000..7077a9e --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/TakeWhile.cs @@ -0,0 +1,118 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class TakeWhile + { + internal sealed class Predicate : Producer + { + private readonly IObservable _source; + private readonly Func _predicate; + + public Predicate(IObservable source, Func predicate) + { + _source = source; + _predicate = predicate; + } + + protected override _ CreateSink(IObserver observer) => new(_predicate, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly Func _predicate; + private bool _running; + + public _(Func predicate, IObserver observer) + : base(observer) + { + _predicate = predicate; + _running = true; + } + + public override void OnNext(TSource value) + { + if (_running) + { + try + { + _running = _predicate(value); + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + if (_running) + { + ForwardOnNext(value); + } + else + { + ForwardOnCompleted(); + } + } + } + } + } + + internal sealed class PredicateIndexed : Producer + { + private readonly IObservable _source; + private readonly Func _predicate; + + public PredicateIndexed(IObservable source, Func predicate) + { + _source = source; + _predicate = predicate; + } + + protected override _ CreateSink(IObserver observer) => new(_predicate, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly Func _predicate; + private bool _running; + private int _index; + + public _(Func predicate, IObserver observer) + : base(observer) + { + _predicate = predicate; + _running = true; + } + + public override void OnNext(TSource value) + { + if (_running) + { + try + { + _running = _predicate(value, checked(_index++)); + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + if (_running) + { + ForwardOnNext(value); + } + else + { + ForwardOnCompleted(); + } + } + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Throttle.cs b/LibExternal/System.Reactive/Linq/Observable/Throttle.cs new file mode 100644 index 0000000..5b579e0 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Throttle.cs @@ -0,0 +1,275 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Throttle : Producer._> + { + private readonly IObservable _source; + private readonly TimeSpan _dueTime; + private readonly IScheduler _scheduler; + + public Throttle(IObservable source, TimeSpan dueTime, IScheduler scheduler) + { + _source = source; + _dueTime = dueTime; + _scheduler = scheduler; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly object _gate = new(); + private readonly TimeSpan _dueTime; + private readonly IScheduler _scheduler; + + public _(Throttle parent, IObserver observer) + : base(observer) + { + _dueTime = parent._dueTime; + _scheduler = parent._scheduler; + } + + private TSource? _value; + private bool _hasValue; + private SerialDisposableValue _serialCancelable; + private ulong _id; + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _serialCancelable.Dispose(); + } + + base.Dispose(disposing); + } + + public override void OnNext(TSource value) + { + ulong currentid; + + lock (_gate) + { + _hasValue = true; + _value = value; + _id = unchecked(_id + 1); + currentid = _id; + } + + _serialCancelable.Disposable = null; + _serialCancelable.Disposable = _scheduler.ScheduleAction((@this: this, currentid), _dueTime, static tuple => tuple.@this.Propagate(tuple.currentid)); + } + + private void Propagate(ulong currentid) + { + lock (_gate) + { + if (_hasValue && _id == currentid) + { + ForwardOnNext(_value!); + + _hasValue = false; + } + } + } + + public override void OnError(Exception error) + { + _serialCancelable.Dispose(); + + lock (_gate) + { + ForwardOnError(error); + + _hasValue = false; + _id = unchecked(_id + 1); + } + } + + public override void OnCompleted() + { + _serialCancelable.Dispose(); + + lock (_gate) + { + if (_hasValue) + { + ForwardOnNext(_value!); + } + + ForwardOnCompleted(); + + _hasValue = false; + _id = unchecked(_id + 1); + } + } + } + } + + internal sealed class Throttle : Producer._> + { + private readonly IObservable _source; + private readonly Func> _throttleSelector; + + public Throttle(IObservable source, Func> throttleSelector) + { + _source = source; + _throttleSelector = throttleSelector; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly object _gate = new(); + private readonly Func> _throttleSelector; + + public _(Throttle parent, IObserver observer) + : base(observer) + { + _throttleSelector = parent._throttleSelector; + } + + private TSource? _value; + private bool _hasValue; + private SerialDisposableValue _serialCancelable; + private ulong _id; + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _serialCancelable.Dispose(); + } + + base.Dispose(disposing); + } + + public override void OnNext(TSource value) + { + IObservable throttle; + try + { + throttle = _throttleSelector(value); + } + catch (Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + + return; + } + + ulong currentid; + + lock (_gate) + { + _hasValue = true; + _value = value; + _id = unchecked(_id + 1); + currentid = _id; + } + + _serialCancelable.Disposable = null; + + var newInnerObserver = new ThrottleObserver(this, value, currentid); + newInnerObserver.SetResource(throttle.SubscribeSafe(newInnerObserver)); + + _serialCancelable.Disposable = newInnerObserver; + } + + public override void OnError(Exception error) + { + _serialCancelable.Dispose(); + + lock (_gate) + { + ForwardOnError(error); + + _hasValue = false; + _id = unchecked(_id + 1); + } + } + + public override void OnCompleted() + { + _serialCancelable.Dispose(); + + lock (_gate) + { + if (_hasValue) + { + ForwardOnNext(_value!); + } + + ForwardOnCompleted(); + + _hasValue = false; + _id = unchecked(_id + 1); + } + } + + private sealed class ThrottleObserver : SafeObserver + { + private readonly _ _parent; + private readonly TSource _value; + private readonly ulong _currentid; + + public ThrottleObserver(_ parent, TSource value, ulong currentid) + { + _parent = parent; + _value = value; + _currentid = currentid; + } + + public override void OnNext(TThrottle value) + { + lock (_parent._gate) + { + if (_parent._hasValue && _parent._id == _currentid) + { + _parent.ForwardOnNext(_value); + } + + _parent._hasValue = false; + Dispose(); + } + } + + public override void OnError(Exception error) + { + lock (_parent._gate) + { + _parent.ForwardOnError(error); + } + } + + public override void OnCompleted() + { + lock (_parent._gate) + { + if (_parent._hasValue && _parent._id == _currentid) + { + _parent.ForwardOnNext(_value); + } + + _parent._hasValue = false; + Dispose(); + } + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Throw.cs b/LibExternal/System.Reactive/Linq/Observable/Throw.cs new file mode 100644 index 0000000..72fc691 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Throw.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Throw : Producer._> + { + private readonly Exception _exception; + private readonly IScheduler _scheduler; + + public Throw(Exception exception, IScheduler scheduler) + { + _exception = exception; + _scheduler = scheduler; + } + + protected override _ CreateSink(IObserver observer) => new(_exception, observer); + + protected override void Run(_ sink) => sink.Run(_scheduler); + + internal sealed class _ : IdentitySink + { + private readonly Exception _exception; + + public _(Exception exception, IObserver observer) + : base(observer) + { + _exception = exception; + } + + public void Run(IScheduler scheduler) + { + SetUpstream(scheduler.ScheduleAction(this, static @this => @this.ForwardOnError(@this._exception))); + } + } + } + + // There is no need for a full Producer/IdentitySink as there is no + // way to stop a first task running on the immediate scheduler + // as it is always synchronous. + internal sealed class ThrowImmediate : BasicProducer + { + private readonly Exception _exception; + + public ThrowImmediate(Exception exception) + { + _exception = exception; + } + + protected override IDisposable Run(IObserver observer) + { + observer.OnError(_exception); + return Disposable.Empty; + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/TimeInterval.cs b/LibExternal/System.Reactive/Linq/Observable/TimeInterval.cs new file mode 100644 index 0000000..5cb21c4 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/TimeInterval.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class TimeInterval : Producer, TimeInterval._> + { + private readonly IObservable _source; + private readonly IScheduler _scheduler; + + public TimeInterval(IObservable source, IScheduler scheduler) + { + _source = source; + _scheduler = scheduler; + } + + protected override _ CreateSink(IObserver> observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : Sink> + { + public _(IObserver> observer) + : base(observer) + { + } + + private IStopwatch? _watch; + private TimeSpan _last; + + public void Run(TimeInterval parent) + { + _watch = parent._scheduler.StartStopwatch(); + _last = TimeSpan.Zero; + + SetUpstream(parent._source.Subscribe(this)); + } + + public override void OnNext(TSource value) + { + var now = _watch!.Elapsed; // NB: Watch is assigned during Run. + var span = now.Subtract(_last); + _last = now; + ForwardOnNext(new Reactive.TimeInterval(value, span)); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Timeout.cs b/LibExternal/System.Reactive/Linq/Observable/Timeout.cs new file mode 100644 index 0000000..e149f55 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Timeout.cs @@ -0,0 +1,382 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reactive.Disposables; +using System.Threading; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class Timeout + { + internal sealed class Relative : Producer + { + private readonly IObservable _source; + private readonly TimeSpan _dueTime; + private readonly IObservable _other; + private readonly IScheduler _scheduler; + + public Relative(IObservable source, TimeSpan dueTime, IObservable other, IScheduler scheduler) + { + _source = source; + _dueTime = dueTime; + _other = other; + _scheduler = scheduler; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly TimeSpan _dueTime; + private readonly IObservable _other; + private readonly IScheduler _scheduler; + + private long _index; + private SingleAssignmentDisposableValue _mainDisposable; + private SingleAssignmentDisposableValue _otherDisposable; + private IDisposable? _timerDisposable; + + public _(Relative parent, IObserver observer) + : base(observer) + { + _dueTime = parent._dueTime; + _other = parent._other; + _scheduler = parent._scheduler; + } + + public override void Run(IObservable source) + { + CreateTimer(0L); + + _mainDisposable.Disposable = source.SubscribeSafe(this); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _mainDisposable.Dispose(); + _otherDisposable.Dispose(); + Disposable.Dispose(ref _timerDisposable); + } + + base.Dispose(disposing); + } + + private void CreateTimer(long idx) + { + if (Disposable.TrySetMultiple(ref _timerDisposable, null)) + { + var d = _scheduler.ScheduleAction((idx, instance: this), _dueTime, static state => { state.instance.Timeout(state.idx); }); + + Disposable.TrySetMultiple(ref _timerDisposable, d); + } + } + + private void Timeout(long idx) + { + if (Volatile.Read(ref _index) == idx && Interlocked.CompareExchange(ref _index, long.MaxValue, idx) == idx) + { + _mainDisposable.Dispose(); + + var d = _other.Subscribe(GetForwarder()); + + _otherDisposable.Disposable = d; + } + } + + public override void OnNext(TSource value) + { + var idx = Volatile.Read(ref _index); + if (idx != long.MaxValue && Interlocked.CompareExchange(ref _index, idx + 1, idx) == idx) + { + // Do not swap in the BooleanDisposable.True here + // As we'll need _timerDisposable to store the next timer + // BD.True would cancel it immediately and break the operation + Volatile.Read(ref _timerDisposable)?.Dispose(); + + ForwardOnNext(value); + + CreateTimer(idx + 1); + } + } + + public override void OnError(Exception error) + { + if (Interlocked.Exchange(ref _index, long.MaxValue) != long.MaxValue) + { + Disposable.Dispose(ref _timerDisposable); + + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + if (Interlocked.Exchange(ref _index, long.MaxValue) != long.MaxValue) + { + Disposable.Dispose(ref _timerDisposable); + + ForwardOnCompleted(); + } + } + } + } + + internal sealed class Absolute : Producer + { + private readonly IObservable _source; + private readonly DateTimeOffset _dueTime; + private readonly IObservable _other; + private readonly IScheduler _scheduler; + + public Absolute(IObservable source, DateTimeOffset dueTime, IObservable other, IScheduler scheduler) + { + _source = source; + _dueTime = dueTime; + _other = other; + _scheduler = scheduler; + } + + protected override _ CreateSink(IObserver observer) => new(_other, observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : IdentitySink + { + private readonly IObservable _other; + + private SerialDisposableValue _serialDisposable; + private int _wip; + + public _(IObservable other, IObserver observer) + : base(observer) + { + _other = other; + } + + public void Run(Absolute parent) + { + SetUpstream(parent._scheduler.ScheduleAction(this, parent._dueTime, static @this => @this.Timeout())); + + _serialDisposable.Disposable = parent._source.SubscribeSafe(this); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _serialDisposable.Dispose(); + } + + base.Dispose(disposing); + } + + private void Timeout() + { + if (Interlocked.Increment(ref _wip) == 1) + { + _serialDisposable.Disposable = _other.SubscribeSafe(GetForwarder()); + } + } + + public override void OnNext(TSource value) + { + if (Interlocked.CompareExchange(ref _wip, 1, 0) == 0) + { + ForwardOnNext(value); + if (Interlocked.Decrement(ref _wip) != 0) + { + _serialDisposable.Disposable = _other.SubscribeSafe(GetForwarder()); + } + } + } + + public override void OnError(Exception error) + { + if (Interlocked.CompareExchange(ref _wip, 1, 0) == 0) + { + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + if (Interlocked.CompareExchange(ref _wip, 1, 0) == 0) + { + ForwardOnCompleted(); + } + } + } + } + } + + internal sealed class Timeout : Producer._> + { + private readonly IObservable _source; + private readonly IObservable _firstTimeout; + private readonly Func> _timeoutSelector; + private readonly IObservable _other; + + public Timeout(IObservable source, IObservable firstTimeout, Func> timeoutSelector, IObservable other) + { + _source = source; + _firstTimeout = firstTimeout; + _timeoutSelector = timeoutSelector; + _other = other; + } + + protected override _ CreateSink(IObserver observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : IdentitySink + { + private readonly Func> _timeoutSelector; + private readonly IObservable _other; + + private SerialDisposableValue _sourceDisposable; + private IDisposable? _timerDisposable; + private long _index; + + public _(Timeout parent, IObserver observer) + : base(observer) + { + _timeoutSelector = parent._timeoutSelector; + _other = parent._other; + } + + public void Run(Timeout parent) + { + SetTimer(parent._firstTimeout, 0L); + + _sourceDisposable.TrySetFirst(parent._source.SubscribeSafe(this)); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _sourceDisposable.Dispose(); + Disposable.Dispose(ref _timerDisposable); + } + + base.Dispose(disposing); + } + + public override void OnNext(TSource value) + { + var idx = Volatile.Read(ref _index); + if (idx != long.MaxValue) + { + if (Interlocked.CompareExchange(ref _index, idx + 1, idx) == idx) + { + // Do not use Disposable.TryDispose here, we need the field + // for the next timer + Volatile.Read(ref _timerDisposable)?.Dispose(); + + ForwardOnNext(value); + + IObservable timeoutSource; + try + { + timeoutSource = _timeoutSelector(value); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + SetTimer(timeoutSource, idx + 1); + } + } + } + + public override void OnError(Exception error) + { + if (Interlocked.Exchange(ref _index, long.MaxValue) != long.MaxValue) + { + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + if (Interlocked.Exchange(ref _index, long.MaxValue) != long.MaxValue) + { + ForwardOnCompleted(); + } + } + + private void Timeout(long idx) + { + if (Volatile.Read(ref _index) == idx + && Interlocked.CompareExchange(ref _index, long.MaxValue, idx) == idx) + { + _sourceDisposable.Disposable = _other.SubscribeSafe(GetForwarder()); + } + } + + private bool TimeoutError(long idx, Exception error) + { + if (Volatile.Read(ref _index) == idx + && Interlocked.CompareExchange(ref _index, long.MaxValue, idx) == idx) + { + ForwardOnError(error); + return true; + } + + return false; + } + + private void SetTimer(IObservable timeout, long idx) + { + var timeoutObserver = new TimeoutObserver(this, idx); + + if (Disposable.TrySetSerial(ref _timerDisposable, timeoutObserver)) + { + var d = timeout.Subscribe(timeoutObserver); + timeoutObserver.SetResource(d); + } + } + + private sealed class TimeoutObserver : SafeObserver + { + private readonly _ _parent; + private readonly long _id; + + public TimeoutObserver(_ parent, long id) + { + _parent = parent; + _id = id; + } + + public override void OnNext(TTimeout value) + { + OnCompleted(); + } + + public override void OnError(Exception error) + { + if (!_parent.TimeoutError(_id, error)) + { + Dispose(); + } + } + + public override void OnCompleted() + { + _parent.Timeout(_id); + + Dispose(); + } + + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Timer.cs b/LibExternal/System.Reactive/Linq/Observable/Timer.cs new file mode 100644 index 0000000..a8e4f0b --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Timer.cs @@ -0,0 +1,302 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reactive.Disposables; +using System.Threading; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class Timer + { + internal abstract class Single : Producer + { + private readonly IScheduler _scheduler; + + protected Single(IScheduler scheduler) + { + _scheduler = scheduler; + } + + internal sealed class Relative : Single + { + private readonly TimeSpan _dueTime; + + public Relative(TimeSpan dueTime, IScheduler scheduler) + : base(scheduler) + { + _dueTime = dueTime; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(this, _dueTime); + } + + internal sealed class Absolute : Single + { + private readonly DateTimeOffset _dueTime; + + public Absolute(DateTimeOffset dueTime, IScheduler scheduler) + : base(scheduler) + { + _dueTime = dueTime; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(this, _dueTime); + } + + internal sealed class _ : IdentitySink + { + public _(IObserver observer) + : base(observer) + { + } + + public void Run(Single parent, DateTimeOffset dueTime) + { + SetUpstream(parent._scheduler.ScheduleAction(this, dueTime, state => state.Invoke())); + } + + public void Run(Single parent, TimeSpan dueTime) + { + SetUpstream(parent._scheduler.ScheduleAction(this, dueTime, state => state.Invoke())); + } + + private void Invoke() + { + ForwardOnNext(0); + ForwardOnCompleted(); + } + } + } + + internal abstract class Periodic : Producer + { + private readonly TimeSpan _period; + private readonly IScheduler _scheduler; + + protected Periodic(TimeSpan period, IScheduler scheduler) + { + _period = period; + _scheduler = scheduler; + } + + internal sealed class Relative : Periodic + { + private readonly TimeSpan _dueTime; + + public Relative(TimeSpan dueTime, TimeSpan period, IScheduler scheduler) + : base(period, scheduler) + { + _dueTime = dueTime; + } + + protected override _ CreateSink(IObserver observer) => new(_period, observer); + + protected override void Run(_ sink) => sink.Run(this, _dueTime); + } + + internal sealed class Absolute : Periodic + { + private readonly DateTimeOffset _dueTime; + + public Absolute(DateTimeOffset dueTime, TimeSpan period, IScheduler scheduler) + : base(period, scheduler) + { + _dueTime = dueTime; + } + + protected override _ CreateSink(IObserver observer) => new(_period, observer); + + protected override void Run(_ sink) => sink.Run(this, _dueTime); + } + + internal sealed class _ : IdentitySink + { + private readonly TimeSpan _period; + private long _index; + + public _(TimeSpan period, IObserver observer) + : base(observer) + { + _period = period; + } + + public void Run(Periodic parent, DateTimeOffset dueTime) + { + SetUpstream(parent._scheduler.Schedule(this, dueTime, static (innerScheduler, @this) => @this.InvokeStart(innerScheduler))); + } + + public void Run(Periodic parent, TimeSpan dueTime) + { + // + // Optimize for the case of Observable.Interval. + // + if (dueTime == _period) + { + SetUpstream(parent._scheduler.SchedulePeriodic(this, _period, static @this => @this.Tick())); + } + else + { + SetUpstream(parent._scheduler.Schedule(this, dueTime, static (innerScheduler, @this) => @this.InvokeStart(innerScheduler))); + } + } + + // + // BREAKING CHANGE v2 > v1.x - No more correction for time drift based on absolute time. This + // didn't work for large period values anyway; the fractional + // error exceeded corrections. Also complicated dealing with system + // clock change conditions and caused numerous bugs. + // + // - For more precise scheduling, use a custom scheduler that measures TimeSpan values in a + // better way, e.g. spinning to make up for the last part of the period. Whether or not the + // values of the TimeSpan period match NT time or wall clock time is up to the scheduler. + // + // - For more accurate scheduling wrt the system clock, use Generate with DateTimeOffset time + // selectors. When the system clock changes, intervals will not be the same as diffs between + // consecutive absolute time values. The precision will be low (1s range by default). + // + private void Tick() + { + var count = _index; + _index = unchecked(count + 1); + + ForwardOnNext(count); + } + + private int _pendingTickCount; + private IDisposable? _periodic; + + private IDisposable InvokeStart(IScheduler self) + { + // + // Notice the first call to OnNext will introduce skew if it takes significantly long when + // using the following naive implementation: + // + // Code: base._observer.OnNext(0L); + // return self.SchedulePeriodicEmulated(1L, _period, (Func)Tick); + // + // What we're saying here is that Observable.Timer(dueTime, period) is pretty much the same + // as writing Observable.Timer(dueTime).Concat(Observable.Interval(period)). + // + // Expected: dueTime + // | + // 0--period--1--period--2--period--3--period--4--... + // | + // +-OnNext(0L)-| + // + // Actual: dueTime + // | + // 0------------#--period--1--period--2--period--3--period--4--... + // | + // +-OnNext(0L)-| + // + // Different solutions for this behavior have different problems: + // + // 1. Scheduling the periodic job first and using an AsyncLock to serialize the OnNext calls + // has the drawback that InvokeStart may never return. This happens when every callback + // doesn't meet the period's deadline, hence the periodic job keeps queueing stuff up. In + // this case, InvokeStart stays the owner of the AsyncLock and the call to Wait will never + // return, thus not allowing any interleaving of work on this scheduler's logical thread. + // + // 2. Scheduling the periodic job first and using a (blocking) synchronization primitive to + // signal completion of the OnNext(0L) call to the Tick call requires quite a bit of state + // and careful handling of the case when OnNext(0L) throws. What's worse is the blocking + // behavior inside Tick. + // + // In order to avoid blocking behavior, we need a scheme much like SchedulePeriodic emulation + // where work to dispatch OnNext(n + 1) is delegated to a catch up loop in case OnNext(n) was + // still running. Because SchedulePeriodic emulation exhibits such behavior in all cases, we + // only need to deal with the overlap of OnNext(0L) with future periodic OnNext(n) dispatch + // jobs. In the worst case where every callback takes longer than the deadline implied by the + // period, the periodic job will just queue up work that's dispatched by the tail-recursive + // catch up loop. In the best case, all work will be dispatched on the periodic scheduler. + // + + // + // We start with one tick pending because we're about to start doing OnNext(0L). + // + _pendingTickCount = 1; + + var d = new SingleAssignmentDisposable(); + _periodic = d; + _index = 1; + d.Disposable = self.SchedulePeriodic(this, _period, static @this => @this.Tock()); + + try + { + ForwardOnNext(0L); + } + catch (Exception e) + { + d.Dispose(); + e.Throw(); + } + + // + // If the periodic scheduling job already ran before we finished dispatching the OnNext(0L) + // call, we'll find pendingTickCount to be > 1. In this case, we need to catch up by dispatching + // subsequent calls to OnNext as fast as possible, but without running a loop in order to ensure + // fair play with the scheduler. So, we run a tail-recursive loop in CatchUp instead. + // + if (Interlocked.Decrement(ref _pendingTickCount) > 0) + { + var c = self.Schedule((@this: this, index: 1L), static (tuple, action) => tuple.@this.CatchUp(tuple.index, action)); + + return StableCompositeDisposable.Create(d, c); + } + + return d; + } + + private void Tock() + { + // + // Notice the handler for (emulated) periodic scheduling is non-reentrant. + // + // When there's no overlap with the OnNext(0L) call, the following code will cycle through + // pendingTickCount 0 -> 1 -> 0 for the remainder of the timer's execution. + // + // If there's overlap with the OnNext(0L) call, pendingTickCount will increase to record + // the number of catch up OnNext calls required, which will be dispatched by the recursive + // scheduling loop in CatchUp (which quits when it reaches 0 pending ticks). + // + if (Interlocked.Increment(ref _pendingTickCount) == 1) + { + var count = _index; + _index = unchecked(count + 1); + + ForwardOnNext(count); + Interlocked.Decrement(ref _pendingTickCount); + } + } + + private void CatchUp(long count, Action<(_, long)> recurse) + { + try + { + ForwardOnNext(count); + } + catch (Exception e) + { + _periodic!.Dispose(); // NB: _periodic is assigned before this runs. + e.Throw(); + } + + // + // We can simply bail out if we decreased the tick count to 0. In that case, the Tock + // method will take over when it sees the 0 -> 1 transition. + // + if (Interlocked.Decrement(ref _pendingTickCount) > 0) + { + recurse((this, unchecked(count + 1))); + } + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Timestamp.cs b/LibExternal/System.Reactive/Linq/Observable/Timestamp.cs new file mode 100644 index 0000000..f055047 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Timestamp.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Timestamp : Producer, Timestamp._> + { + private readonly IObservable _source; + private readonly IScheduler _scheduler; + + public Timestamp(IObservable source, IScheduler scheduler) + { + _source = source; + _scheduler = scheduler; + } + + protected override _ CreateSink(IObserver> observer) => new(_scheduler, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink> + { + private readonly IScheduler _scheduler; + + public _(IScheduler scheduler, IObserver> observer) + : base(observer) + { + _scheduler = scheduler; + } + + public override void OnNext(TSource value) + { + ForwardOnNext(new Timestamped(value, _scheduler.Now)); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/ToArray.cs b/LibExternal/System.Reactive/Linq/Observable/ToArray.cs new file mode 100644 index 0000000..80d4085 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/ToArray.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class ToArray : Producer._> + { + private readonly IObservable _source; + + public ToArray(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink + { + private List _list; + + public _(IObserver observer) + : base(observer) + { + _list = new List(); + } + + public override void OnNext(TSource value) + { + _list.Add(value); + } + + public override void OnError(Exception error) + { + Cleanup(); + base.OnError(error); + } + + public override void OnCompleted() + { + var list = _list; + Cleanup(); + ForwardOnNext(list.ToArray()); + ForwardOnCompleted(); + } + + private void Cleanup() + { + _list = null!; + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/ToDictionary.cs b/LibExternal/System.Reactive/Linq/Observable/ToDictionary.cs new file mode 100644 index 0000000..1df8d64 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/ToDictionary.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class ToDictionary : Producer, ToDictionary._> + where TKey : notnull + { + private readonly IObservable _source; + private readonly Func _keySelector; + private readonly Func _elementSelector; + private readonly IEqualityComparer _comparer; + + public ToDictionary(IObservable source, Func keySelector, Func elementSelector, IEqualityComparer comparer) + { + _source = source; + _keySelector = keySelector; + _elementSelector = elementSelector; + _comparer = comparer; + } + + protected override _ CreateSink(IObserver> observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink> + { + private readonly Func _keySelector; + private readonly Func _elementSelector; + private Dictionary _dictionary; + + public _(ToDictionary parent, IObserver> observer) + : base(observer) + { + _keySelector = parent._keySelector; + _elementSelector = parent._elementSelector; + _dictionary = new Dictionary(parent._comparer); + } + + public override void OnNext(TSource value) + { + try + { + _dictionary.Add(_keySelector(value), _elementSelector(value)); + } + catch (Exception ex) + { + Cleanup(); + ForwardOnError(ex); + } + } + + public override void OnError(Exception error) + { + Cleanup(); + ForwardOnError(error); + } + + public override void OnCompleted() + { + var dictionary = _dictionary; + Cleanup(); + ForwardOnNext(dictionary); + ForwardOnCompleted(); + } + + private void Cleanup() + { + _dictionary = null!; + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/ToList.cs b/LibExternal/System.Reactive/Linq/Observable/ToList.cs new file mode 100644 index 0000000..95cac9d --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/ToList.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class ToList : Producer, ToList._> + { + private readonly IObservable _source; + + public ToList(IObservable source) + { + _source = source; + } + + protected override _ CreateSink(IObserver> observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink> + { + private List _list; + + public _(IObserver> observer) + : base(observer) + { + _list = new List(); + } + + public override void OnNext(TSource value) + { + _list.Add(value); + } + + public override void OnError(Exception error) + { + Cleanup(); + ForwardOnError(error); + } + + public override void OnCompleted() + { + var list = _list; + Cleanup(); + ForwardOnNext(list); + ForwardOnCompleted(); + } + + private void Cleanup() + { + _list = null!; + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/ToLookup.cs b/LibExternal/System.Reactive/Linq/Observable/ToLookup.cs new file mode 100644 index 0000000..1add64f --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/ToLookup.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class ToLookup : Producer, ToLookup._> + { + private readonly IObservable _source; + private readonly Func _keySelector; + private readonly Func _elementSelector; + private readonly IEqualityComparer _comparer; + + public ToLookup(IObservable source, Func keySelector, Func elementSelector, IEqualityComparer comparer) + { + _source = source; + _keySelector = keySelector; + _elementSelector = elementSelector; + _comparer = comparer; + } + + protected override _ CreateSink(IObserver> observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink> + { + private readonly Func _keySelector; + private readonly Func _elementSelector; + private Lookup _lookup; + + public _(ToLookup parent, IObserver> observer) + : base(observer) + { + _keySelector = parent._keySelector; + _elementSelector = parent._elementSelector; + _lookup = new Lookup(parent._comparer); + } + + public override void OnNext(TSource value) + { + try + { + _lookup.Add(_keySelector(value), _elementSelector(value)); + } + catch (Exception ex) + { + Cleanup(); + ForwardOnError(ex); + } + } + + public override void OnError(Exception error) + { + Cleanup(); + ForwardOnError(error); + } + + public override void OnCompleted() + { + var lookup = _lookup; + Cleanup(); + ForwardOnNext(lookup); + ForwardOnCompleted(); + } + + private void Cleanup() + { + _lookup = null!; + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/ToObservable.cs b/LibExternal/System.Reactive/Linq/Observable/ToObservable.cs new file mode 100644 index 0000000..2acc885 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/ToObservable.cs @@ -0,0 +1,209 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Concurrency; +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class ToObservableRecursive : Producer._> + { + private readonly IEnumerable _source; + private readonly IScheduler _scheduler; + + public ToObservableRecursive(IEnumerable source, IScheduler scheduler) + { + _source = source; + _scheduler = scheduler; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source, _scheduler); + + internal sealed class _ : IdentitySink + { + private IEnumerator? _enumerator; + + private volatile bool _disposed; + + public _(IObserver observer) + : base(observer) + { + } + + public void Run(IEnumerable source, IScheduler scheduler) + { + try + { + _enumerator = source.GetEnumerator(); + } + catch (Exception exception) + { + ForwardOnError(exception); + + return; + } + + // + // We never allow the scheduled work to be cancelled. Instead, the _disposed flag + // is used to have LoopRec bail out and perform proper clean-up of the + // enumerator. + // + scheduler.Schedule(this, static (innerScheduler, @this) => @this.LoopRec(innerScheduler)); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _disposed = true; + } + } + + private IDisposable LoopRec(IScheduler scheduler) + { + var hasNext = false; + var ex = default(Exception); + var current = default(TSource); + + var enumerator = _enumerator!; // NB: Loop only runs after enumerator is assigned. + + if (_disposed) + { + enumerator.Dispose(); + _enumerator = null; + + return Disposable.Empty; + } + + try + { + hasNext = enumerator.MoveNext(); + if (hasNext) + { + current = enumerator.Current; + } + } + catch (Exception exception) + { + ex = exception; + } + + if (ex != null) + { + enumerator.Dispose(); + _enumerator = null; + + ForwardOnError(ex); + return Disposable.Empty; + } + + if (!hasNext) + { + enumerator.Dispose(); + _enumerator = null; + + ForwardOnCompleted(); + return Disposable.Empty; + } + + ForwardOnNext(current!); // NB: Non-null when hasNext is true. + + // + // We never allow the scheduled work to be cancelled. Instead, the _disposed flag + // is used to have LoopRec bail out and perform proper clean-up of the + // enumerator. + // + scheduler.Schedule(this, static (innerScheduler, @this) => @this.LoopRec(innerScheduler)); + + return Disposable.Empty; + } + } + } + + internal sealed class ToObservableLongRunning : Producer._> + { + private readonly IEnumerable _source; + private readonly ISchedulerLongRunning _scheduler; + + public ToObservableLongRunning(IEnumerable source, ISchedulerLongRunning scheduler) + { + _source = source; + _scheduler = scheduler; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_source, _scheduler); + + internal sealed class _ : IdentitySink + { + public _(IObserver observer) + : base(observer) + { + } + + public void Run(IEnumerable source, ISchedulerLongRunning scheduler) + { + IEnumerator e; + try + { + e = source.GetEnumerator(); + } + catch (Exception exception) + { + ForwardOnError(exception); + + return; + } + + SetUpstream(scheduler.ScheduleLongRunning((@this: this, e), static (tuple, cancelable) => tuple.@this.Loop(tuple.e, cancelable))); + } + + private void Loop(IEnumerator enumerator, ICancelable cancel) + { + while (!cancel.IsDisposed) + { + var hasNext = false; + var ex = default(Exception); + var current = default(TSource); + + try + { + hasNext = enumerator.MoveNext(); + if (hasNext) + { + current = enumerator.Current; + } + } + catch (Exception exception) + { + ex = exception; + } + + if (ex != null) + { + ForwardOnError(ex); + break; + } + + if (!hasNext) + { + ForwardOnCompleted(); + break; + } + + ForwardOnNext(current!); // NB: Non-null when hasNext is true. + } + + enumerator.Dispose(); + Dispose(); + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Using.cs b/LibExternal/System.Reactive/Linq/Observable/Using.cs new file mode 100644 index 0000000..8095864 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Using.cs @@ -0,0 +1,80 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class Using : Producer._> + where TResource : IDisposable + { + private readonly Func _resourceFactory; + private readonly Func> _observableFactory; + + public Using(Func resourceFactory, Func> observableFactory) + { + _resourceFactory = resourceFactory; + _observableFactory = observableFactory; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : IdentitySink + { + public _(IObserver observer) + : base(observer) + { + } + + private SingleAssignmentDisposableValue _disposable; + + public void Run(Using parent) + { + IObservable source; + var disposable = Disposable.Empty; + try + { + var resource = parent._resourceFactory(); + if (resource != null) + { + disposable = resource; + } + + // + // NB: We do allow the factory to return `null`, similar to the `using` statement in C#. However, we don't want to bother + // users with a TResource? parameter and cause a breaking change to their code, even if their factory returns non-null. + // Right now, we can't track non-null state across the invocation of resourceFactory into observableFactory. If we'd + // be able to do that, it would make sense to warn users about a possible null. In the absence of this, we'd end up + // with a lot of false positives (in fact, most code would cause a warning), and force users to pollute their code with + // the "damn-it" ! operator. + // + + source = parent._observableFactory(resource!); + } + catch (Exception exception) + { + source = Observable.Throw(exception); + } + + // It is important to set the disposable resource after + // Run(). In the synchronous case this would else dispose + // the the resource before the source subscription. + Run(source); + _disposable.Disposable = disposable; + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _disposable.Dispose(); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Where.cs b/LibExternal/System.Reactive/Linq/Observable/Where.cs new file mode 100644 index 0000000..9e81a75 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Where.cs @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class Where + { + internal sealed class Predicate : Producer + { + private readonly IObservable _source; + private readonly Func _predicate; + + public Predicate(IObservable source, Func predicate) + { + _source = source; + _predicate = predicate; + } + + public IObservable Combine(Func predicate) + { + return new Predicate(_source, x => _predicate(x) && predicate(x)); + } + + protected override _ CreateSink(IObserver observer) => new(_predicate, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly Func _predicate; + + public _(Func predicate, IObserver observer) + : base(observer) + { + _predicate = predicate; + } + + public override void OnNext(TSource value) + { + var shouldRun = false; + try + { + shouldRun = _predicate(value); + } + catch (Exception exception) + { + ForwardOnError(exception); + return; + } + + if (shouldRun) + { + ForwardOnNext(value); + } + } + } + } + + internal sealed class PredicateIndexed : Producer + { + private readonly IObservable _source; + private readonly Func _predicate; + + public PredicateIndexed(IObservable source, Func predicate) + { + _source = source; + _predicate = predicate; + } + + protected override _ CreateSink(IObserver observer) => new(_predicate, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : IdentitySink + { + private readonly Func _predicate; + private int _index; + + public _(Func predicate, IObserver observer) + : base(observer) + { + _predicate = predicate; + } + + public override void OnNext(TSource value) + { + var shouldRun = false; + try + { + shouldRun = _predicate(value, checked(_index++)); + } + catch (Exception exception) + { + ForwardOnError(exception); + } + + if (shouldRun) + { + ForwardOnNext(value); + } + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/While.cs b/LibExternal/System.Reactive/Linq/Observable/While.cs new file mode 100644 index 0000000..3bd5df0 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/While.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class While : Producer._>, IConcatenatable + { + private readonly Func _condition; + private readonly IObservable _source; + + public While(Func condition, IObservable source) + { + _condition = condition; + _source = source; + } + + protected override _ CreateSink(IObserver observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(GetSources()); + + public IEnumerable> GetSources() + { + while (_condition()) + { + yield return _source; + } + } + + internal sealed class _ : ConcatSink + { + public _(IObserver observer) + : base(observer) + { + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Window.cs b/LibExternal/System.Reactive/Linq/Observable/Window.cs new file mode 100644 index 0000000..42eda26 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Window.cs @@ -0,0 +1,766 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Concurrency; +using System.Reactive.Disposables; +using System.Reactive.Subjects; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal static class Window + { + internal sealed class Count : Producer, Count._> + { + private readonly IObservable _source; + private readonly int _count; + private readonly int _skip; + + public Count(IObservable source, int count, int skip) + { + _source = source; + _count = count; + _skip = skip; + } + + protected override _ CreateSink(IObserver> observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink> + { + private readonly Queue> _queue = new(); + private readonly SingleAssignmentDisposable _m = new(); + private readonly RefCountDisposable _refCountDisposable; + + private readonly int _count; + private readonly int _skip; + + public _(Count parent, IObserver> observer) + : base(observer) + { + _refCountDisposable = new RefCountDisposable(_m); + + _count = parent._count; + _skip = parent._skip; + } + + private int _n; + + public override void Run(IObservable source) + { + var firstWindow = CreateWindow(); + ForwardOnNext(firstWindow); + + _m.Disposable = source.SubscribeSafe(this); + + SetUpstream(_refCountDisposable); + } + + private IObservable CreateWindow() + { + var s = new Subject(); + _queue.Enqueue(s); + return new WindowObservable(s, _refCountDisposable); + } + + public override void OnNext(TSource value) + { + foreach (var s in _queue) + { + s.OnNext(value); + } + + var c = _n - _count + 1; + if (c >= 0 && c % _skip == 0) + { + var s = _queue.Dequeue(); + s.OnCompleted(); + } + + _n++; + if (_n % _skip == 0) + { + var newWindow = CreateWindow(); + ForwardOnNext(newWindow); + } + } + + public override void OnError(Exception error) + { + while (_queue.Count > 0) + { + _queue.Dequeue().OnError(error); + } + + ForwardOnError(error); + } + + public override void OnCompleted() + { + while (_queue.Count > 0) + { + _queue.Dequeue().OnCompleted(); + } + + ForwardOnCompleted(); + } + } + } + + internal sealed class TimeSliding : Producer, TimeSliding._> + { + private readonly IObservable _source; + private readonly TimeSpan _timeSpan; + private readonly TimeSpan _timeShift; + private readonly IScheduler _scheduler; + + public TimeSliding(IObservable source, TimeSpan timeSpan, TimeSpan timeShift, IScheduler scheduler) + { + _source = source; + _timeSpan = timeSpan; + _timeShift = timeShift; + _scheduler = scheduler; + } + + protected override _ CreateSink(IObserver> observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : Sink> + { + private readonly object _gate = new(); + private readonly Queue> _q = new(); + private readonly SerialDisposable _timerD = new(); + + private readonly IScheduler _scheduler; + private readonly TimeSpan _timeShift; + + public _(TimeSliding parent, IObserver> observer) + : base(observer) + { + _scheduler = parent._scheduler; + _timeShift = parent._timeShift; + } + + private RefCountDisposable? _refCountDisposable; + private TimeSpan _totalTime; + private TimeSpan _nextShift; + private TimeSpan _nextSpan; + + public void Run(TimeSliding parent) + { + _totalTime = TimeSpan.Zero; + _nextShift = parent._timeShift; + _nextSpan = parent._timeSpan; + + var groupDisposable = new CompositeDisposable(2) { _timerD }; + _refCountDisposable = new RefCountDisposable(groupDisposable); + + CreateWindow(); + CreateTimer(); + + groupDisposable.Add(parent._source.SubscribeSafe(this)); + + SetUpstream(_refCountDisposable); + } + + private void CreateWindow() + { + var s = new Subject(); + _q.Enqueue(s); + ForwardOnNext(new WindowObservable(s, _refCountDisposable!)); // NB: _refCountDisposable gets assigned in Run. + } + + private void CreateTimer() + { + var m = new SingleAssignmentDisposable(); + _timerD.Disposable = m; + + var isSpan = false; + var isShift = false; + if (_nextSpan == _nextShift) + { + isSpan = true; + isShift = true; + } + else if (_nextSpan < _nextShift) + { + isSpan = true; + } + else + { + isShift = true; + } + + var newTotalTime = isSpan ? _nextSpan : _nextShift; + var ts = newTotalTime - _totalTime; + _totalTime = newTotalTime; + + if (isSpan) + { + _nextSpan += _timeShift; + } + + if (isShift) + { + _nextShift += _timeShift; + } + + m.Disposable = _scheduler.ScheduleAction((@this: this, isSpan, isShift), ts, static tuple => tuple.@this.Tick(tuple.isSpan, tuple.isShift)); + } + + private void Tick(bool isSpan, bool isShift) + { + lock (_gate) + { + // + // BREAKING CHANGE v2 > v1.x - Making behavior of sending OnCompleted to the window + // before sending out a new window consistent across all + // overloads of Window and Buffer. Before v2, the two + // operations below were reversed. + // + if (isSpan) + { + var s = _q.Dequeue(); + s.OnCompleted(); + } + + if (isShift) + { + CreateWindow(); + } + } + + CreateTimer(); + } + + public override void OnNext(TSource value) + { + lock (_gate) + { + foreach (var s in _q) + { + s.OnNext(value); + } + } + } + + public override void OnError(Exception error) + { + lock (_gate) + { + foreach (var s in _q) + { + s.OnError(error); + } + + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + lock (_gate) + { + foreach (var s in _q) + { + s.OnCompleted(); + } + + ForwardOnCompleted(); + } + } + } + } + + internal sealed class TimeHopping : Producer, TimeHopping._> + { + private readonly IObservable _source; + private readonly TimeSpan _timeSpan; + private readonly IScheduler _scheduler; + + public TimeHopping(IObservable source, TimeSpan timeSpan, IScheduler scheduler) + { + _source = source; + _timeSpan = timeSpan; + _scheduler = scheduler; + } + + protected override _ CreateSink(IObserver> observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : Sink> + { + private readonly object _gate = new(); + private Subject _subject; + + public _(IObserver> observer) + : base(observer) + { + _subject = new Subject(); + } + + private RefCountDisposable? _refCountDisposable; + + public void Run(TimeHopping parent) + { + var groupDisposable = new CompositeDisposable(2); + _refCountDisposable = new RefCountDisposable(groupDisposable); + + NextWindow(); + + groupDisposable.Add(parent._scheduler.SchedulePeriodic(this, parent._timeSpan, static @this => @this.Tick())); + groupDisposable.Add(parent._source.SubscribeSafe(this)); + + SetUpstream(_refCountDisposable); + } + + private void Tick() + { + lock (_gate) + { + _subject.OnCompleted(); + + _subject = new Subject(); + NextWindow(); + } + } + + private void NextWindow() + { + ForwardOnNext(new WindowObservable(_subject, _refCountDisposable!)); // NB: _refCountDisposable gets assigned in Run. + } + + public override void OnNext(TSource value) + { + lock (_gate) + { + _subject.OnNext(value); + } + } + + public override void OnError(Exception error) + { + lock (_gate) + { + _subject.OnError(error); + + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + lock (_gate) + { + _subject.OnCompleted(); + + ForwardOnCompleted(); + } + } + } + } + + internal sealed class Ferry : Producer, Ferry._> + { + private readonly IObservable _source; + private readonly int _count; + private readonly TimeSpan _timeSpan; + private readonly IScheduler _scheduler; + + public Ferry(IObservable source, TimeSpan timeSpan, int count, IScheduler scheduler) + { + _source = source; + _timeSpan = timeSpan; + _count = count; + _scheduler = scheduler; + } + + protected override _ CreateSink(IObserver> observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink> + { + private readonly object _gate = new(); + private readonly SerialDisposable _timerD = new(); + + private readonly int _count; + private readonly TimeSpan _timeSpan; + private readonly IScheduler _scheduler; + + private Subject _s; + + public _(Ferry parent, IObserver> observer) + : base(observer) + { + _count = parent._count; + _timeSpan = parent._timeSpan; + _scheduler = parent._scheduler; + + _s = new Subject(); + } + + private int _n; + + private RefCountDisposable? _refCountDisposable; + + public override void Run(IObservable source) + { + var groupDisposable = new CompositeDisposable(2) { _timerD }; + _refCountDisposable = new RefCountDisposable(groupDisposable); + + NextWindow(); + CreateTimer(_s); + + groupDisposable.Add(source.SubscribeSafe(this)); + + SetUpstream(_refCountDisposable); + } + + private void CreateTimer(Subject window) + { + var m = new SingleAssignmentDisposable(); + _timerD.Disposable = m; + + m.Disposable = _scheduler.ScheduleAction((@this: this, window), _timeSpan, static tuple => tuple.@this.Tick(tuple.window)); + } + + private void NextWindow() + { + ForwardOnNext(new WindowObservable(_s, _refCountDisposable!)); // NB: _refCountDisposable gets assigned in Run. + } + + private void Tick(Subject window) + { + Subject newWindow; + + lock (_gate) + { + if (window != _s) + { + return; + } + + _n = 0; + newWindow = new Subject(); + + _s.OnCompleted(); + _s = newWindow; + NextWindow(); + } + + CreateTimer(newWindow); + } + + public override void OnNext(TSource value) + { + Subject? newWindow = null; + + lock (_gate) + { + _s.OnNext(value); + + _n++; + if (_n == _count) + { + _n = 0; + newWindow = new Subject(); + + _s.OnCompleted(); + _s = newWindow; + NextWindow(); + } + } + + if (newWindow != null) + { + CreateTimer(newWindow); + } + } + + public override void OnError(Exception error) + { + lock (_gate) + { + _s.OnError(error); + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + lock (_gate) + { + _s.OnCompleted(); + ForwardOnCompleted(); + } + } + } + } + } + + internal static class Window + { + internal sealed class Selector : Producer, Selector._> + { + private readonly IObservable _source; + private readonly Func> _windowClosingSelector; + + public Selector(IObservable source, Func> windowClosingSelector) + { + _source = source; + _windowClosingSelector = windowClosingSelector; + } + + protected override _ CreateSink(IObserver> observer) => new(this, observer); + + protected override void Run(_ sink) => sink.Run(_source); + + internal sealed class _ : Sink> + { + private readonly object _gate = new(); + private readonly AsyncLock _windowGate = new(); + private readonly SerialDisposable _m = new(); + private readonly Func> _windowClosingSelector; + + private Subject _window; + + public _(Selector parent, IObserver> observer) + : base(observer) + { + _windowClosingSelector = parent._windowClosingSelector; + + _window = new Subject(); + } + + private RefCountDisposable? _refCountDisposable; + + public override void Run(IObservable source) + { + var groupDisposable = new CompositeDisposable(2) { _m }; + _refCountDisposable = new RefCountDisposable(groupDisposable); + + NextWindow(); + + groupDisposable.Add(source.SubscribeSafe(this)); + + _windowGate.Wait(this, static @this => @this.CreateWindowClose()); + + SetUpstream(_refCountDisposable); + } + + private void NextWindow() + { + var window = new WindowObservable(_window, _refCountDisposable!); // NB: _refCountDisposable gets assigned in Run. + ForwardOnNext(window); + } + + private void CreateWindowClose() + { + IObservable windowClose; + try + { + windowClose = _windowClosingSelector(); + } + catch (Exception exception) + { + lock (_gate) + { + ForwardOnError(exception); + } + return; + } + + var observer = new WindowClosingObserver(this); + _m.Disposable = observer; + observer.SetResource(windowClose.SubscribeSafe(observer)); + } + + private void CloseWindow(IDisposable closingSubscription) + { + closingSubscription.Dispose(); + + lock (_gate) + { + _window.OnCompleted(); + _window = new Subject(); + + NextWindow(); + } + + _windowGate.Wait(this, static @this => @this.CreateWindowClose()); + } + + private sealed class WindowClosingObserver : SafeObserver + { + private readonly _ _parent; + + public WindowClosingObserver(_ parent) + { + _parent = parent; + } + + public override void OnNext(TWindowClosing value) + { + _parent.CloseWindow(this); + } + + public override void OnError(Exception error) + { + _parent.OnError(error); + } + + public override void OnCompleted() + { + _parent.CloseWindow(this); + } + } + + public override void OnNext(TSource value) + { + lock (_gate) + { + _window.OnNext(value); + } + } + + public override void OnError(Exception error) + { + lock (_gate) + { + _window.OnError(error); + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + lock (_gate) + { + _window.OnCompleted(); + ForwardOnCompleted(); + } + } + } + } + + internal sealed class Boundaries : Producer, Boundaries._> + { + private readonly IObservable _source; + private readonly IObservable _windowBoundaries; + + public Boundaries(IObservable source, IObservable windowBoundaries) + { + _source = source; + _windowBoundaries = windowBoundaries; + } + + protected override _ CreateSink(IObserver> observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(this); + + internal sealed class _ : Sink> + { + private readonly object _gate = new(); + + private Subject _window; + + public _(IObserver> observer) + : base(observer) + { + _window = new Subject(); + } + + private RefCountDisposable? _refCountDisposable; + + public void Run(Boundaries parent) + { + var d = new CompositeDisposable(2); + _refCountDisposable = new RefCountDisposable(d); + + NextWindow(); + + d.Add(parent._source.SubscribeSafe(this)); + d.Add(parent._windowBoundaries.SubscribeSafe(new WindowClosingObserver(this))); + + SetUpstream(_refCountDisposable); + } + + private void NextWindow() + { + var window = new WindowObservable(_window, _refCountDisposable!); // NB: _refCountDisposable gets assigned in Run. + ForwardOnNext(window); + } + + private sealed class WindowClosingObserver : IObserver + { + private readonly _ _parent; + + public WindowClosingObserver(_ parent) + { + _parent = parent; + } + + public void OnNext(TWindowClosing value) + { + lock (_parent._gate) + { + _parent._window.OnCompleted(); + _parent._window = new Subject(); + + _parent.NextWindow(); + } + } + + public void OnError(Exception error) + { + _parent.OnError(error); + } + + public void OnCompleted() + { + _parent.OnCompleted(); + } + } + + public override void OnNext(TSource value) + { + lock (_gate) + { + _window.OnNext(value); + } + } + + public override void OnError(Exception error) + { + lock (_gate) + { + _window.OnError(error); + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + lock (_gate) + { + _window.OnCompleted(); + ForwardOnCompleted(); + } + } + } + } + } + + internal sealed class WindowObservable : AddRef + { + public WindowObservable(IObservable source, RefCountDisposable refCount) + : base(source, refCount) + { + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/WithLatestFrom.cs b/LibExternal/System.Reactive/Linq/Observable/WithLatestFrom.cs new file mode 100644 index 0000000..051a3bf --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/WithLatestFrom.cs @@ -0,0 +1,160 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + internal sealed class WithLatestFrom : Producer._> + { + private readonly IObservable _first; + private readonly IObservable _second; + private readonly Func _resultSelector; + + public WithLatestFrom(IObservable first, IObservable second, Func resultSelector) + { + _first = first; + _second = second; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_first, _second); + + internal sealed class _ : IdentitySink + { + private readonly object _gate = new(); + private readonly object _latestGate = new(); + + private readonly Func _resultSelector; + + public _(Func resultSelector, IObserver observer) + : base(observer) + { + _resultSelector = resultSelector; + } + + private volatile bool _hasLatest; + private TSecond? _latest; + + private SingleAssignmentDisposableValue _secondDisposable; + + public void Run(IObservable first, IObservable second) + { + var fstO = new FirstObserver(this); + var sndO = new SecondObserver(this); + + _secondDisposable.Disposable = second.SubscribeSafe(sndO); + SetUpstream(first.SubscribeSafe(fstO)); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _secondDisposable.Dispose(); + } + + base.Dispose(disposing); + } + + private sealed class FirstObserver : IObserver + { + private readonly _ _parent; + + public FirstObserver(_ parent) + { + _parent = parent; + } + + public void OnCompleted() + { + lock (_parent._gate) + { + _parent.ForwardOnCompleted(); + } + } + + public void OnError(Exception error) + { + lock (_parent._gate) + { + _parent.ForwardOnError(error); + } + } + + public void OnNext(TFirst value) + { + if (_parent._hasLatest) // Volatile read + { + TSecond latest; + + lock (_parent._latestGate) + { + latest = _parent._latest!; // NB: Not null when hasLatest is true. + } + + TResult res; + + try + { + res = _parent._resultSelector(value, latest); + } + catch (Exception ex) + { + lock (_parent._gate) + { + _parent.ForwardOnError(ex); + } + + return; + } + + lock (_parent._gate) + { + _parent.ForwardOnNext(res); + } + } + } + } + + private sealed class SecondObserver : IObserver + { + private readonly _ _parent; + + public SecondObserver(_ parent) + { + _parent = parent; + } + + public void OnCompleted() + { + _parent._secondDisposable.Dispose(); + } + + public void OnError(Exception error) + { + lock (_parent._gate) + { + _parent.ForwardOnError(error); + } + } + + public void OnNext(TSecond value) + { + lock (_parent._latestGate) + { + _parent._latest = value; + } + + if (!_parent._hasLatest) + { + _parent._hasLatest = true; + } + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Zip.NAry.cs b/LibExternal/System.Reactive/Linq/Observable/Zip.NAry.cs new file mode 100644 index 0000000..e14a1bd --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Zip.NAry.cs @@ -0,0 +1,1601 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + #region [3,16]-ary + + /* The following code is generated by a T4 template. */ + + #region Zip auto-generated code (10/2/2020 12:26:57 PM) + + internal sealed class Zip : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly Func _resultSelector; + + public Zip(IObservable source1, IObservable source2, IObservable source3, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3); + + internal sealed class _ : ZipSink + { + private readonly Func _resultSelector; + + private readonly ZipObserver _observer1; + private readonly ZipObserver _observer2; + private readonly ZipObserver _observer3; + + public _(Func resultSelector, IObserver observer) + : base(3, observer) + { + _resultSelector = resultSelector; + + _observer1 = new ZipObserver(_gate, this, 0); + _observer2 = new ZipObserver(_gate, this, 1); + _observer3 = new ZipObserver(_gate, this, 2); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3) + { + var disposables = new IDisposable[3]; + + disposables[0] = _observer1; + Queues[0] = _observer1.Values; + + disposables[1] = _observer2; + Queues[1] = _observer2.Values; + + disposables[2] = _observer3; + Queues[2] = _observer3.Values; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(disposables)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Values.Dequeue(), _observer2.Values.Dequeue(), _observer3.Values.Dequeue()); + } + } + + internal sealed class Zip : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly Func _resultSelector; + + public Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4); + + internal sealed class _ : ZipSink + { + private readonly Func _resultSelector; + + private readonly ZipObserver _observer1; + private readonly ZipObserver _observer2; + private readonly ZipObserver _observer3; + private readonly ZipObserver _observer4; + + public _(Func resultSelector, IObserver observer) + : base(4, observer) + { + _resultSelector = resultSelector; + + _observer1 = new ZipObserver(_gate, this, 0); + _observer2 = new ZipObserver(_gate, this, 1); + _observer3 = new ZipObserver(_gate, this, 2); + _observer4 = new ZipObserver(_gate, this, 3); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4) + { + var disposables = new IDisposable[4]; + + disposables[0] = _observer1; + Queues[0] = _observer1.Values; + + disposables[1] = _observer2; + Queues[1] = _observer2.Values; + + disposables[2] = _observer3; + Queues[2] = _observer3.Values; + + disposables[3] = _observer4; + Queues[3] = _observer4.Values; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(disposables)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Values.Dequeue(), _observer2.Values.Dequeue(), _observer3.Values.Dequeue(), _observer4.Values.Dequeue()); + } + } + + internal sealed class Zip : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly IObservable _source5; + private readonly Func _resultSelector; + + public Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _source5 = source5; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4, _source5); + + internal sealed class _ : ZipSink + { + private readonly Func _resultSelector; + + private readonly ZipObserver _observer1; + private readonly ZipObserver _observer2; + private readonly ZipObserver _observer3; + private readonly ZipObserver _observer4; + private readonly ZipObserver _observer5; + + public _(Func resultSelector, IObserver observer) + : base(5, observer) + { + _resultSelector = resultSelector; + + _observer1 = new ZipObserver(_gate, this, 0); + _observer2 = new ZipObserver(_gate, this, 1); + _observer3 = new ZipObserver(_gate, this, 2); + _observer4 = new ZipObserver(_gate, this, 3); + _observer5 = new ZipObserver(_gate, this, 4); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5) + { + var disposables = new IDisposable[5]; + + disposables[0] = _observer1; + Queues[0] = _observer1.Values; + + disposables[1] = _observer2; + Queues[1] = _observer2.Values; + + disposables[2] = _observer3; + Queues[2] = _observer3.Values; + + disposables[3] = _observer4; + Queues[3] = _observer4.Values; + + disposables[4] = _observer5; + Queues[4] = _observer5.Values; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + _observer5.SetResource(source5.SubscribeSafe(_observer5)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(disposables)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Values.Dequeue(), _observer2.Values.Dequeue(), _observer3.Values.Dequeue(), _observer4.Values.Dequeue(), _observer5.Values.Dequeue()); + } + } + + internal sealed class Zip : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly IObservable _source5; + private readonly IObservable _source6; + private readonly Func _resultSelector; + + public Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _source5 = source5; + _source6 = source6; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4, _source5, _source6); + + internal sealed class _ : ZipSink + { + private readonly Func _resultSelector; + + private readonly ZipObserver _observer1; + private readonly ZipObserver _observer2; + private readonly ZipObserver _observer3; + private readonly ZipObserver _observer4; + private readonly ZipObserver _observer5; + private readonly ZipObserver _observer6; + + public _(Func resultSelector, IObserver observer) + : base(6, observer) + { + _resultSelector = resultSelector; + + _observer1 = new ZipObserver(_gate, this, 0); + _observer2 = new ZipObserver(_gate, this, 1); + _observer3 = new ZipObserver(_gate, this, 2); + _observer4 = new ZipObserver(_gate, this, 3); + _observer5 = new ZipObserver(_gate, this, 4); + _observer6 = new ZipObserver(_gate, this, 5); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6) + { + var disposables = new IDisposable[6]; + + disposables[0] = _observer1; + Queues[0] = _observer1.Values; + + disposables[1] = _observer2; + Queues[1] = _observer2.Values; + + disposables[2] = _observer3; + Queues[2] = _observer3.Values; + + disposables[3] = _observer4; + Queues[3] = _observer4.Values; + + disposables[4] = _observer5; + Queues[4] = _observer5.Values; + + disposables[5] = _observer6; + Queues[5] = _observer6.Values; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + _observer5.SetResource(source5.SubscribeSafe(_observer5)); + _observer6.SetResource(source6.SubscribeSafe(_observer6)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(disposables)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Values.Dequeue(), _observer2.Values.Dequeue(), _observer3.Values.Dequeue(), _observer4.Values.Dequeue(), _observer5.Values.Dequeue(), _observer6.Values.Dequeue()); + } + } + + internal sealed class Zip : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly IObservable _source5; + private readonly IObservable _source6; + private readonly IObservable _source7; + private readonly Func _resultSelector; + + public Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _source5 = source5; + _source6 = source6; + _source7 = source7; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4, _source5, _source6, _source7); + + internal sealed class _ : ZipSink + { + private readonly Func _resultSelector; + + private readonly ZipObserver _observer1; + private readonly ZipObserver _observer2; + private readonly ZipObserver _observer3; + private readonly ZipObserver _observer4; + private readonly ZipObserver _observer5; + private readonly ZipObserver _observer6; + private readonly ZipObserver _observer7; + + public _(Func resultSelector, IObserver observer) + : base(7, observer) + { + _resultSelector = resultSelector; + + _observer1 = new ZipObserver(_gate, this, 0); + _observer2 = new ZipObserver(_gate, this, 1); + _observer3 = new ZipObserver(_gate, this, 2); + _observer4 = new ZipObserver(_gate, this, 3); + _observer5 = new ZipObserver(_gate, this, 4); + _observer6 = new ZipObserver(_gate, this, 5); + _observer7 = new ZipObserver(_gate, this, 6); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7) + { + var disposables = new IDisposable[7]; + + disposables[0] = _observer1; + Queues[0] = _observer1.Values; + + disposables[1] = _observer2; + Queues[1] = _observer2.Values; + + disposables[2] = _observer3; + Queues[2] = _observer3.Values; + + disposables[3] = _observer4; + Queues[3] = _observer4.Values; + + disposables[4] = _observer5; + Queues[4] = _observer5.Values; + + disposables[5] = _observer6; + Queues[5] = _observer6.Values; + + disposables[6] = _observer7; + Queues[6] = _observer7.Values; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + _observer5.SetResource(source5.SubscribeSafe(_observer5)); + _observer6.SetResource(source6.SubscribeSafe(_observer6)); + _observer7.SetResource(source7.SubscribeSafe(_observer7)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(disposables)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Values.Dequeue(), _observer2.Values.Dequeue(), _observer3.Values.Dequeue(), _observer4.Values.Dequeue(), _observer5.Values.Dequeue(), _observer6.Values.Dequeue(), _observer7.Values.Dequeue()); + } + } + + internal sealed class Zip : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly IObservable _source5; + private readonly IObservable _source6; + private readonly IObservable _source7; + private readonly IObservable _source8; + private readonly Func _resultSelector; + + public Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _source5 = source5; + _source6 = source6; + _source7 = source7; + _source8 = source8; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4, _source5, _source6, _source7, _source8); + + internal sealed class _ : ZipSink + { + private readonly Func _resultSelector; + + private readonly ZipObserver _observer1; + private readonly ZipObserver _observer2; + private readonly ZipObserver _observer3; + private readonly ZipObserver _observer4; + private readonly ZipObserver _observer5; + private readonly ZipObserver _observer6; + private readonly ZipObserver _observer7; + private readonly ZipObserver _observer8; + + public _(Func resultSelector, IObserver observer) + : base(8, observer) + { + _resultSelector = resultSelector; + + _observer1 = new ZipObserver(_gate, this, 0); + _observer2 = new ZipObserver(_gate, this, 1); + _observer3 = new ZipObserver(_gate, this, 2); + _observer4 = new ZipObserver(_gate, this, 3); + _observer5 = new ZipObserver(_gate, this, 4); + _observer6 = new ZipObserver(_gate, this, 5); + _observer7 = new ZipObserver(_gate, this, 6); + _observer8 = new ZipObserver(_gate, this, 7); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8) + { + var disposables = new IDisposable[8]; + + disposables[0] = _observer1; + Queues[0] = _observer1.Values; + + disposables[1] = _observer2; + Queues[1] = _observer2.Values; + + disposables[2] = _observer3; + Queues[2] = _observer3.Values; + + disposables[3] = _observer4; + Queues[3] = _observer4.Values; + + disposables[4] = _observer5; + Queues[4] = _observer5.Values; + + disposables[5] = _observer6; + Queues[5] = _observer6.Values; + + disposables[6] = _observer7; + Queues[6] = _observer7.Values; + + disposables[7] = _observer8; + Queues[7] = _observer8.Values; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + _observer5.SetResource(source5.SubscribeSafe(_observer5)); + _observer6.SetResource(source6.SubscribeSafe(_observer6)); + _observer7.SetResource(source7.SubscribeSafe(_observer7)); + _observer8.SetResource(source8.SubscribeSafe(_observer8)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(disposables)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Values.Dequeue(), _observer2.Values.Dequeue(), _observer3.Values.Dequeue(), _observer4.Values.Dequeue(), _observer5.Values.Dequeue(), _observer6.Values.Dequeue(), _observer7.Values.Dequeue(), _observer8.Values.Dequeue()); + } + } + + internal sealed class Zip : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly IObservable _source5; + private readonly IObservable _source6; + private readonly IObservable _source7; + private readonly IObservable _source8; + private readonly IObservable _source9; + private readonly Func _resultSelector; + + public Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _source5 = source5; + _source6 = source6; + _source7 = source7; + _source8 = source8; + _source9 = source9; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4, _source5, _source6, _source7, _source8, _source9); + + internal sealed class _ : ZipSink + { + private readonly Func _resultSelector; + + private readonly ZipObserver _observer1; + private readonly ZipObserver _observer2; + private readonly ZipObserver _observer3; + private readonly ZipObserver _observer4; + private readonly ZipObserver _observer5; + private readonly ZipObserver _observer6; + private readonly ZipObserver _observer7; + private readonly ZipObserver _observer8; + private readonly ZipObserver _observer9; + + public _(Func resultSelector, IObserver observer) + : base(9, observer) + { + _resultSelector = resultSelector; + + _observer1 = new ZipObserver(_gate, this, 0); + _observer2 = new ZipObserver(_gate, this, 1); + _observer3 = new ZipObserver(_gate, this, 2); + _observer4 = new ZipObserver(_gate, this, 3); + _observer5 = new ZipObserver(_gate, this, 4); + _observer6 = new ZipObserver(_gate, this, 5); + _observer7 = new ZipObserver(_gate, this, 6); + _observer8 = new ZipObserver(_gate, this, 7); + _observer9 = new ZipObserver(_gate, this, 8); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9) + { + var disposables = new IDisposable[9]; + + disposables[0] = _observer1; + Queues[0] = _observer1.Values; + + disposables[1] = _observer2; + Queues[1] = _observer2.Values; + + disposables[2] = _observer3; + Queues[2] = _observer3.Values; + + disposables[3] = _observer4; + Queues[3] = _observer4.Values; + + disposables[4] = _observer5; + Queues[4] = _observer5.Values; + + disposables[5] = _observer6; + Queues[5] = _observer6.Values; + + disposables[6] = _observer7; + Queues[6] = _observer7.Values; + + disposables[7] = _observer8; + Queues[7] = _observer8.Values; + + disposables[8] = _observer9; + Queues[8] = _observer9.Values; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + _observer5.SetResource(source5.SubscribeSafe(_observer5)); + _observer6.SetResource(source6.SubscribeSafe(_observer6)); + _observer7.SetResource(source7.SubscribeSafe(_observer7)); + _observer8.SetResource(source8.SubscribeSafe(_observer8)); + _observer9.SetResource(source9.SubscribeSafe(_observer9)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(disposables)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Values.Dequeue(), _observer2.Values.Dequeue(), _observer3.Values.Dequeue(), _observer4.Values.Dequeue(), _observer5.Values.Dequeue(), _observer6.Values.Dequeue(), _observer7.Values.Dequeue(), _observer8.Values.Dequeue(), _observer9.Values.Dequeue()); + } + } + + internal sealed class Zip : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly IObservable _source5; + private readonly IObservable _source6; + private readonly IObservable _source7; + private readonly IObservable _source8; + private readonly IObservable _source9; + private readonly IObservable _source10; + private readonly Func _resultSelector; + + public Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _source5 = source5; + _source6 = source6; + _source7 = source7; + _source8 = source8; + _source9 = source9; + _source10 = source10; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4, _source5, _source6, _source7, _source8, _source9, _source10); + + internal sealed class _ : ZipSink + { + private readonly Func _resultSelector; + + private readonly ZipObserver _observer1; + private readonly ZipObserver _observer2; + private readonly ZipObserver _observer3; + private readonly ZipObserver _observer4; + private readonly ZipObserver _observer5; + private readonly ZipObserver _observer6; + private readonly ZipObserver _observer7; + private readonly ZipObserver _observer8; + private readonly ZipObserver _observer9; + private readonly ZipObserver _observer10; + + public _(Func resultSelector, IObserver observer) + : base(10, observer) + { + _resultSelector = resultSelector; + + _observer1 = new ZipObserver(_gate, this, 0); + _observer2 = new ZipObserver(_gate, this, 1); + _observer3 = new ZipObserver(_gate, this, 2); + _observer4 = new ZipObserver(_gate, this, 3); + _observer5 = new ZipObserver(_gate, this, 4); + _observer6 = new ZipObserver(_gate, this, 5); + _observer7 = new ZipObserver(_gate, this, 6); + _observer8 = new ZipObserver(_gate, this, 7); + _observer9 = new ZipObserver(_gate, this, 8); + _observer10 = new ZipObserver(_gate, this, 9); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10) + { + var disposables = new IDisposable[10]; + + disposables[0] = _observer1; + Queues[0] = _observer1.Values; + + disposables[1] = _observer2; + Queues[1] = _observer2.Values; + + disposables[2] = _observer3; + Queues[2] = _observer3.Values; + + disposables[3] = _observer4; + Queues[3] = _observer4.Values; + + disposables[4] = _observer5; + Queues[4] = _observer5.Values; + + disposables[5] = _observer6; + Queues[5] = _observer6.Values; + + disposables[6] = _observer7; + Queues[6] = _observer7.Values; + + disposables[7] = _observer8; + Queues[7] = _observer8.Values; + + disposables[8] = _observer9; + Queues[8] = _observer9.Values; + + disposables[9] = _observer10; + Queues[9] = _observer10.Values; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + _observer5.SetResource(source5.SubscribeSafe(_observer5)); + _observer6.SetResource(source6.SubscribeSafe(_observer6)); + _observer7.SetResource(source7.SubscribeSafe(_observer7)); + _observer8.SetResource(source8.SubscribeSafe(_observer8)); + _observer9.SetResource(source9.SubscribeSafe(_observer9)); + _observer10.SetResource(source10.SubscribeSafe(_observer10)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(disposables)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Values.Dequeue(), _observer2.Values.Dequeue(), _observer3.Values.Dequeue(), _observer4.Values.Dequeue(), _observer5.Values.Dequeue(), _observer6.Values.Dequeue(), _observer7.Values.Dequeue(), _observer8.Values.Dequeue(), _observer9.Values.Dequeue(), _observer10.Values.Dequeue()); + } + } + + internal sealed class Zip : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly IObservable _source5; + private readonly IObservable _source6; + private readonly IObservable _source7; + private readonly IObservable _source8; + private readonly IObservable _source9; + private readonly IObservable _source10; + private readonly IObservable _source11; + private readonly Func _resultSelector; + + public Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _source5 = source5; + _source6 = source6; + _source7 = source7; + _source8 = source8; + _source9 = source9; + _source10 = source10; + _source11 = source11; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4, _source5, _source6, _source7, _source8, _source9, _source10, _source11); + + internal sealed class _ : ZipSink + { + private readonly Func _resultSelector; + + private readonly ZipObserver _observer1; + private readonly ZipObserver _observer2; + private readonly ZipObserver _observer3; + private readonly ZipObserver _observer4; + private readonly ZipObserver _observer5; + private readonly ZipObserver _observer6; + private readonly ZipObserver _observer7; + private readonly ZipObserver _observer8; + private readonly ZipObserver _observer9; + private readonly ZipObserver _observer10; + private readonly ZipObserver _observer11; + + public _(Func resultSelector, IObserver observer) + : base(11, observer) + { + _resultSelector = resultSelector; + + _observer1 = new ZipObserver(_gate, this, 0); + _observer2 = new ZipObserver(_gate, this, 1); + _observer3 = new ZipObserver(_gate, this, 2); + _observer4 = new ZipObserver(_gate, this, 3); + _observer5 = new ZipObserver(_gate, this, 4); + _observer6 = new ZipObserver(_gate, this, 5); + _observer7 = new ZipObserver(_gate, this, 6); + _observer8 = new ZipObserver(_gate, this, 7); + _observer9 = new ZipObserver(_gate, this, 8); + _observer10 = new ZipObserver(_gate, this, 9); + _observer11 = new ZipObserver(_gate, this, 10); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11) + { + var disposables = new IDisposable[11]; + + disposables[0] = _observer1; + Queues[0] = _observer1.Values; + + disposables[1] = _observer2; + Queues[1] = _observer2.Values; + + disposables[2] = _observer3; + Queues[2] = _observer3.Values; + + disposables[3] = _observer4; + Queues[3] = _observer4.Values; + + disposables[4] = _observer5; + Queues[4] = _observer5.Values; + + disposables[5] = _observer6; + Queues[5] = _observer6.Values; + + disposables[6] = _observer7; + Queues[6] = _observer7.Values; + + disposables[7] = _observer8; + Queues[7] = _observer8.Values; + + disposables[8] = _observer9; + Queues[8] = _observer9.Values; + + disposables[9] = _observer10; + Queues[9] = _observer10.Values; + + disposables[10] = _observer11; + Queues[10] = _observer11.Values; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + _observer5.SetResource(source5.SubscribeSafe(_observer5)); + _observer6.SetResource(source6.SubscribeSafe(_observer6)); + _observer7.SetResource(source7.SubscribeSafe(_observer7)); + _observer8.SetResource(source8.SubscribeSafe(_observer8)); + _observer9.SetResource(source9.SubscribeSafe(_observer9)); + _observer10.SetResource(source10.SubscribeSafe(_observer10)); + _observer11.SetResource(source11.SubscribeSafe(_observer11)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(disposables)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Values.Dequeue(), _observer2.Values.Dequeue(), _observer3.Values.Dequeue(), _observer4.Values.Dequeue(), _observer5.Values.Dequeue(), _observer6.Values.Dequeue(), _observer7.Values.Dequeue(), _observer8.Values.Dequeue(), _observer9.Values.Dequeue(), _observer10.Values.Dequeue(), _observer11.Values.Dequeue()); + } + } + + internal sealed class Zip : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly IObservable _source5; + private readonly IObservable _source6; + private readonly IObservable _source7; + private readonly IObservable _source8; + private readonly IObservable _source9; + private readonly IObservable _source10; + private readonly IObservable _source11; + private readonly IObservable _source12; + private readonly Func _resultSelector; + + public Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _source5 = source5; + _source6 = source6; + _source7 = source7; + _source8 = source8; + _source9 = source9; + _source10 = source10; + _source11 = source11; + _source12 = source12; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4, _source5, _source6, _source7, _source8, _source9, _source10, _source11, _source12); + + internal sealed class _ : ZipSink + { + private readonly Func _resultSelector; + + private readonly ZipObserver _observer1; + private readonly ZipObserver _observer2; + private readonly ZipObserver _observer3; + private readonly ZipObserver _observer4; + private readonly ZipObserver _observer5; + private readonly ZipObserver _observer6; + private readonly ZipObserver _observer7; + private readonly ZipObserver _observer8; + private readonly ZipObserver _observer9; + private readonly ZipObserver _observer10; + private readonly ZipObserver _observer11; + private readonly ZipObserver _observer12; + + public _(Func resultSelector, IObserver observer) + : base(12, observer) + { + _resultSelector = resultSelector; + + _observer1 = new ZipObserver(_gate, this, 0); + _observer2 = new ZipObserver(_gate, this, 1); + _observer3 = new ZipObserver(_gate, this, 2); + _observer4 = new ZipObserver(_gate, this, 3); + _observer5 = new ZipObserver(_gate, this, 4); + _observer6 = new ZipObserver(_gate, this, 5); + _observer7 = new ZipObserver(_gate, this, 6); + _observer8 = new ZipObserver(_gate, this, 7); + _observer9 = new ZipObserver(_gate, this, 8); + _observer10 = new ZipObserver(_gate, this, 9); + _observer11 = new ZipObserver(_gate, this, 10); + _observer12 = new ZipObserver(_gate, this, 11); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12) + { + var disposables = new IDisposable[12]; + + disposables[0] = _observer1; + Queues[0] = _observer1.Values; + + disposables[1] = _observer2; + Queues[1] = _observer2.Values; + + disposables[2] = _observer3; + Queues[2] = _observer3.Values; + + disposables[3] = _observer4; + Queues[3] = _observer4.Values; + + disposables[4] = _observer5; + Queues[4] = _observer5.Values; + + disposables[5] = _observer6; + Queues[5] = _observer6.Values; + + disposables[6] = _observer7; + Queues[6] = _observer7.Values; + + disposables[7] = _observer8; + Queues[7] = _observer8.Values; + + disposables[8] = _observer9; + Queues[8] = _observer9.Values; + + disposables[9] = _observer10; + Queues[9] = _observer10.Values; + + disposables[10] = _observer11; + Queues[10] = _observer11.Values; + + disposables[11] = _observer12; + Queues[11] = _observer12.Values; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + _observer5.SetResource(source5.SubscribeSafe(_observer5)); + _observer6.SetResource(source6.SubscribeSafe(_observer6)); + _observer7.SetResource(source7.SubscribeSafe(_observer7)); + _observer8.SetResource(source8.SubscribeSafe(_observer8)); + _observer9.SetResource(source9.SubscribeSafe(_observer9)); + _observer10.SetResource(source10.SubscribeSafe(_observer10)); + _observer11.SetResource(source11.SubscribeSafe(_observer11)); + _observer12.SetResource(source12.SubscribeSafe(_observer12)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(disposables)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Values.Dequeue(), _observer2.Values.Dequeue(), _observer3.Values.Dequeue(), _observer4.Values.Dequeue(), _observer5.Values.Dequeue(), _observer6.Values.Dequeue(), _observer7.Values.Dequeue(), _observer8.Values.Dequeue(), _observer9.Values.Dequeue(), _observer10.Values.Dequeue(), _observer11.Values.Dequeue(), _observer12.Values.Dequeue()); + } + } + + internal sealed class Zip : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly IObservable _source5; + private readonly IObservable _source6; + private readonly IObservable _source7; + private readonly IObservable _source8; + private readonly IObservable _source9; + private readonly IObservable _source10; + private readonly IObservable _source11; + private readonly IObservable _source12; + private readonly IObservable _source13; + private readonly Func _resultSelector; + + public Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _source5 = source5; + _source6 = source6; + _source7 = source7; + _source8 = source8; + _source9 = source9; + _source10 = source10; + _source11 = source11; + _source12 = source12; + _source13 = source13; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4, _source5, _source6, _source7, _source8, _source9, _source10, _source11, _source12, _source13); + + internal sealed class _ : ZipSink + { + private readonly Func _resultSelector; + + private readonly ZipObserver _observer1; + private readonly ZipObserver _observer2; + private readonly ZipObserver _observer3; + private readonly ZipObserver _observer4; + private readonly ZipObserver _observer5; + private readonly ZipObserver _observer6; + private readonly ZipObserver _observer7; + private readonly ZipObserver _observer8; + private readonly ZipObserver _observer9; + private readonly ZipObserver _observer10; + private readonly ZipObserver _observer11; + private readonly ZipObserver _observer12; + private readonly ZipObserver _observer13; + + public _(Func resultSelector, IObserver observer) + : base(13, observer) + { + _resultSelector = resultSelector; + + _observer1 = new ZipObserver(_gate, this, 0); + _observer2 = new ZipObserver(_gate, this, 1); + _observer3 = new ZipObserver(_gate, this, 2); + _observer4 = new ZipObserver(_gate, this, 3); + _observer5 = new ZipObserver(_gate, this, 4); + _observer6 = new ZipObserver(_gate, this, 5); + _observer7 = new ZipObserver(_gate, this, 6); + _observer8 = new ZipObserver(_gate, this, 7); + _observer9 = new ZipObserver(_gate, this, 8); + _observer10 = new ZipObserver(_gate, this, 9); + _observer11 = new ZipObserver(_gate, this, 10); + _observer12 = new ZipObserver(_gate, this, 11); + _observer13 = new ZipObserver(_gate, this, 12); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13) + { + var disposables = new IDisposable[13]; + + disposables[0] = _observer1; + Queues[0] = _observer1.Values; + + disposables[1] = _observer2; + Queues[1] = _observer2.Values; + + disposables[2] = _observer3; + Queues[2] = _observer3.Values; + + disposables[3] = _observer4; + Queues[3] = _observer4.Values; + + disposables[4] = _observer5; + Queues[4] = _observer5.Values; + + disposables[5] = _observer6; + Queues[5] = _observer6.Values; + + disposables[6] = _observer7; + Queues[6] = _observer7.Values; + + disposables[7] = _observer8; + Queues[7] = _observer8.Values; + + disposables[8] = _observer9; + Queues[8] = _observer9.Values; + + disposables[9] = _observer10; + Queues[9] = _observer10.Values; + + disposables[10] = _observer11; + Queues[10] = _observer11.Values; + + disposables[11] = _observer12; + Queues[11] = _observer12.Values; + + disposables[12] = _observer13; + Queues[12] = _observer13.Values; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + _observer5.SetResource(source5.SubscribeSafe(_observer5)); + _observer6.SetResource(source6.SubscribeSafe(_observer6)); + _observer7.SetResource(source7.SubscribeSafe(_observer7)); + _observer8.SetResource(source8.SubscribeSafe(_observer8)); + _observer9.SetResource(source9.SubscribeSafe(_observer9)); + _observer10.SetResource(source10.SubscribeSafe(_observer10)); + _observer11.SetResource(source11.SubscribeSafe(_observer11)); + _observer12.SetResource(source12.SubscribeSafe(_observer12)); + _observer13.SetResource(source13.SubscribeSafe(_observer13)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(disposables)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Values.Dequeue(), _observer2.Values.Dequeue(), _observer3.Values.Dequeue(), _observer4.Values.Dequeue(), _observer5.Values.Dequeue(), _observer6.Values.Dequeue(), _observer7.Values.Dequeue(), _observer8.Values.Dequeue(), _observer9.Values.Dequeue(), _observer10.Values.Dequeue(), _observer11.Values.Dequeue(), _observer12.Values.Dequeue(), _observer13.Values.Dequeue()); + } + } + + internal sealed class Zip : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly IObservable _source5; + private readonly IObservable _source6; + private readonly IObservable _source7; + private readonly IObservable _source8; + private readonly IObservable _source9; + private readonly IObservable _source10; + private readonly IObservable _source11; + private readonly IObservable _source12; + private readonly IObservable _source13; + private readonly IObservable _source14; + private readonly Func _resultSelector; + + public Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _source5 = source5; + _source6 = source6; + _source7 = source7; + _source8 = source8; + _source9 = source9; + _source10 = source10; + _source11 = source11; + _source12 = source12; + _source13 = source13; + _source14 = source14; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4, _source5, _source6, _source7, _source8, _source9, _source10, _source11, _source12, _source13, _source14); + + internal sealed class _ : ZipSink + { + private readonly Func _resultSelector; + + private readonly ZipObserver _observer1; + private readonly ZipObserver _observer2; + private readonly ZipObserver _observer3; + private readonly ZipObserver _observer4; + private readonly ZipObserver _observer5; + private readonly ZipObserver _observer6; + private readonly ZipObserver _observer7; + private readonly ZipObserver _observer8; + private readonly ZipObserver _observer9; + private readonly ZipObserver _observer10; + private readonly ZipObserver _observer11; + private readonly ZipObserver _observer12; + private readonly ZipObserver _observer13; + private readonly ZipObserver _observer14; + + public _(Func resultSelector, IObserver observer) + : base(14, observer) + { + _resultSelector = resultSelector; + + _observer1 = new ZipObserver(_gate, this, 0); + _observer2 = new ZipObserver(_gate, this, 1); + _observer3 = new ZipObserver(_gate, this, 2); + _observer4 = new ZipObserver(_gate, this, 3); + _observer5 = new ZipObserver(_gate, this, 4); + _observer6 = new ZipObserver(_gate, this, 5); + _observer7 = new ZipObserver(_gate, this, 6); + _observer8 = new ZipObserver(_gate, this, 7); + _observer9 = new ZipObserver(_gate, this, 8); + _observer10 = new ZipObserver(_gate, this, 9); + _observer11 = new ZipObserver(_gate, this, 10); + _observer12 = new ZipObserver(_gate, this, 11); + _observer13 = new ZipObserver(_gate, this, 12); + _observer14 = new ZipObserver(_gate, this, 13); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14) + { + var disposables = new IDisposable[14]; + + disposables[0] = _observer1; + Queues[0] = _observer1.Values; + + disposables[1] = _observer2; + Queues[1] = _observer2.Values; + + disposables[2] = _observer3; + Queues[2] = _observer3.Values; + + disposables[3] = _observer4; + Queues[3] = _observer4.Values; + + disposables[4] = _observer5; + Queues[4] = _observer5.Values; + + disposables[5] = _observer6; + Queues[5] = _observer6.Values; + + disposables[6] = _observer7; + Queues[6] = _observer7.Values; + + disposables[7] = _observer8; + Queues[7] = _observer8.Values; + + disposables[8] = _observer9; + Queues[8] = _observer9.Values; + + disposables[9] = _observer10; + Queues[9] = _observer10.Values; + + disposables[10] = _observer11; + Queues[10] = _observer11.Values; + + disposables[11] = _observer12; + Queues[11] = _observer12.Values; + + disposables[12] = _observer13; + Queues[12] = _observer13.Values; + + disposables[13] = _observer14; + Queues[13] = _observer14.Values; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + _observer5.SetResource(source5.SubscribeSafe(_observer5)); + _observer6.SetResource(source6.SubscribeSafe(_observer6)); + _observer7.SetResource(source7.SubscribeSafe(_observer7)); + _observer8.SetResource(source8.SubscribeSafe(_observer8)); + _observer9.SetResource(source9.SubscribeSafe(_observer9)); + _observer10.SetResource(source10.SubscribeSafe(_observer10)); + _observer11.SetResource(source11.SubscribeSafe(_observer11)); + _observer12.SetResource(source12.SubscribeSafe(_observer12)); + _observer13.SetResource(source13.SubscribeSafe(_observer13)); + _observer14.SetResource(source14.SubscribeSafe(_observer14)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(disposables)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Values.Dequeue(), _observer2.Values.Dequeue(), _observer3.Values.Dequeue(), _observer4.Values.Dequeue(), _observer5.Values.Dequeue(), _observer6.Values.Dequeue(), _observer7.Values.Dequeue(), _observer8.Values.Dequeue(), _observer9.Values.Dequeue(), _observer10.Values.Dequeue(), _observer11.Values.Dequeue(), _observer12.Values.Dequeue(), _observer13.Values.Dequeue(), _observer14.Values.Dequeue()); + } + } + + internal sealed class Zip : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly IObservable _source5; + private readonly IObservable _source6; + private readonly IObservable _source7; + private readonly IObservable _source8; + private readonly IObservable _source9; + private readonly IObservable _source10; + private readonly IObservable _source11; + private readonly IObservable _source12; + private readonly IObservable _source13; + private readonly IObservable _source14; + private readonly IObservable _source15; + private readonly Func _resultSelector; + + public Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, IObservable source15, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _source5 = source5; + _source6 = source6; + _source7 = source7; + _source8 = source8; + _source9 = source9; + _source10 = source10; + _source11 = source11; + _source12 = source12; + _source13 = source13; + _source14 = source14; + _source15 = source15; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4, _source5, _source6, _source7, _source8, _source9, _source10, _source11, _source12, _source13, _source14, _source15); + + internal sealed class _ : ZipSink + { + private readonly Func _resultSelector; + + private readonly ZipObserver _observer1; + private readonly ZipObserver _observer2; + private readonly ZipObserver _observer3; + private readonly ZipObserver _observer4; + private readonly ZipObserver _observer5; + private readonly ZipObserver _observer6; + private readonly ZipObserver _observer7; + private readonly ZipObserver _observer8; + private readonly ZipObserver _observer9; + private readonly ZipObserver _observer10; + private readonly ZipObserver _observer11; + private readonly ZipObserver _observer12; + private readonly ZipObserver _observer13; + private readonly ZipObserver _observer14; + private readonly ZipObserver _observer15; + + public _(Func resultSelector, IObserver observer) + : base(15, observer) + { + _resultSelector = resultSelector; + + _observer1 = new ZipObserver(_gate, this, 0); + _observer2 = new ZipObserver(_gate, this, 1); + _observer3 = new ZipObserver(_gate, this, 2); + _observer4 = new ZipObserver(_gate, this, 3); + _observer5 = new ZipObserver(_gate, this, 4); + _observer6 = new ZipObserver(_gate, this, 5); + _observer7 = new ZipObserver(_gate, this, 6); + _observer8 = new ZipObserver(_gate, this, 7); + _observer9 = new ZipObserver(_gate, this, 8); + _observer10 = new ZipObserver(_gate, this, 9); + _observer11 = new ZipObserver(_gate, this, 10); + _observer12 = new ZipObserver(_gate, this, 11); + _observer13 = new ZipObserver(_gate, this, 12); + _observer14 = new ZipObserver(_gate, this, 13); + _observer15 = new ZipObserver(_gate, this, 14); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, IObservable source15) + { + var disposables = new IDisposable[15]; + + disposables[0] = _observer1; + Queues[0] = _observer1.Values; + + disposables[1] = _observer2; + Queues[1] = _observer2.Values; + + disposables[2] = _observer3; + Queues[2] = _observer3.Values; + + disposables[3] = _observer4; + Queues[3] = _observer4.Values; + + disposables[4] = _observer5; + Queues[4] = _observer5.Values; + + disposables[5] = _observer6; + Queues[5] = _observer6.Values; + + disposables[6] = _observer7; + Queues[6] = _observer7.Values; + + disposables[7] = _observer8; + Queues[7] = _observer8.Values; + + disposables[8] = _observer9; + Queues[8] = _observer9.Values; + + disposables[9] = _observer10; + Queues[9] = _observer10.Values; + + disposables[10] = _observer11; + Queues[10] = _observer11.Values; + + disposables[11] = _observer12; + Queues[11] = _observer12.Values; + + disposables[12] = _observer13; + Queues[12] = _observer13.Values; + + disposables[13] = _observer14; + Queues[13] = _observer14.Values; + + disposables[14] = _observer15; + Queues[14] = _observer15.Values; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + _observer5.SetResource(source5.SubscribeSafe(_observer5)); + _observer6.SetResource(source6.SubscribeSafe(_observer6)); + _observer7.SetResource(source7.SubscribeSafe(_observer7)); + _observer8.SetResource(source8.SubscribeSafe(_observer8)); + _observer9.SetResource(source9.SubscribeSafe(_observer9)); + _observer10.SetResource(source10.SubscribeSafe(_observer10)); + _observer11.SetResource(source11.SubscribeSafe(_observer11)); + _observer12.SetResource(source12.SubscribeSafe(_observer12)); + _observer13.SetResource(source13.SubscribeSafe(_observer13)); + _observer14.SetResource(source14.SubscribeSafe(_observer14)); + _observer15.SetResource(source15.SubscribeSafe(_observer15)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(disposables)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Values.Dequeue(), _observer2.Values.Dequeue(), _observer3.Values.Dequeue(), _observer4.Values.Dequeue(), _observer5.Values.Dequeue(), _observer6.Values.Dequeue(), _observer7.Values.Dequeue(), _observer8.Values.Dequeue(), _observer9.Values.Dequeue(), _observer10.Values.Dequeue(), _observer11.Values.Dequeue(), _observer12.Values.Dequeue(), _observer13.Values.Dequeue(), _observer14.Values.Dequeue(), _observer15.Values.Dequeue()); + } + } + + internal sealed class Zip : Producer._> + { + private readonly IObservable _source1; + private readonly IObservable _source2; + private readonly IObservable _source3; + private readonly IObservable _source4; + private readonly IObservable _source5; + private readonly IObservable _source6; + private readonly IObservable _source7; + private readonly IObservable _source8; + private readonly IObservable _source9; + private readonly IObservable _source10; + private readonly IObservable _source11; + private readonly IObservable _source12; + private readonly IObservable _source13; + private readonly IObservable _source14; + private readonly IObservable _source15; + private readonly IObservable _source16; + private readonly Func _resultSelector; + + public Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, IObservable source15, IObservable source16, Func resultSelector) + { + _source1 = source1; + _source2 = source2; + _source3 = source3; + _source4 = source4; + _source5 = source5; + _source6 = source6; + _source7 = source7; + _source8 = source8; + _source9 = source9; + _source10 = source10; + _source11 = source11; + _source12 = source12; + _source13 = source13; + _source14 = source14; + _source15 = source15; + _source16 = source16; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_source1, _source2, _source3, _source4, _source5, _source6, _source7, _source8, _source9, _source10, _source11, _source12, _source13, _source14, _source15, _source16); + + internal sealed class _ : ZipSink + { + private readonly Func _resultSelector; + + private readonly ZipObserver _observer1; + private readonly ZipObserver _observer2; + private readonly ZipObserver _observer3; + private readonly ZipObserver _observer4; + private readonly ZipObserver _observer5; + private readonly ZipObserver _observer6; + private readonly ZipObserver _observer7; + private readonly ZipObserver _observer8; + private readonly ZipObserver _observer9; + private readonly ZipObserver _observer10; + private readonly ZipObserver _observer11; + private readonly ZipObserver _observer12; + private readonly ZipObserver _observer13; + private readonly ZipObserver _observer14; + private readonly ZipObserver _observer15; + private readonly ZipObserver _observer16; + + public _(Func resultSelector, IObserver observer) + : base(16, observer) + { + _resultSelector = resultSelector; + + _observer1 = new ZipObserver(_gate, this, 0); + _observer2 = new ZipObserver(_gate, this, 1); + _observer3 = new ZipObserver(_gate, this, 2); + _observer4 = new ZipObserver(_gate, this, 3); + _observer5 = new ZipObserver(_gate, this, 4); + _observer6 = new ZipObserver(_gate, this, 5); + _observer7 = new ZipObserver(_gate, this, 6); + _observer8 = new ZipObserver(_gate, this, 7); + _observer9 = new ZipObserver(_gate, this, 8); + _observer10 = new ZipObserver(_gate, this, 9); + _observer11 = new ZipObserver(_gate, this, 10); + _observer12 = new ZipObserver(_gate, this, 11); + _observer13 = new ZipObserver(_gate, this, 12); + _observer14 = new ZipObserver(_gate, this, 13); + _observer15 = new ZipObserver(_gate, this, 14); + _observer16 = new ZipObserver(_gate, this, 15); + } + + public void Run(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, IObservable source15, IObservable source16) + { + var disposables = new IDisposable[16]; + + disposables[0] = _observer1; + Queues[0] = _observer1.Values; + + disposables[1] = _observer2; + Queues[1] = _observer2.Values; + + disposables[2] = _observer3; + Queues[2] = _observer3.Values; + + disposables[3] = _observer4; + Queues[3] = _observer4.Values; + + disposables[4] = _observer5; + Queues[4] = _observer5.Values; + + disposables[5] = _observer6; + Queues[5] = _observer6.Values; + + disposables[6] = _observer7; + Queues[6] = _observer7.Values; + + disposables[7] = _observer8; + Queues[7] = _observer8.Values; + + disposables[8] = _observer9; + Queues[8] = _observer9.Values; + + disposables[9] = _observer10; + Queues[9] = _observer10.Values; + + disposables[10] = _observer11; + Queues[10] = _observer11.Values; + + disposables[11] = _observer12; + Queues[11] = _observer12.Values; + + disposables[12] = _observer13; + Queues[12] = _observer13.Values; + + disposables[13] = _observer14; + Queues[13] = _observer14.Values; + + disposables[14] = _observer15; + Queues[14] = _observer15.Values; + + disposables[15] = _observer16; + Queues[15] = _observer16.Values; + + _observer1.SetResource(source1.SubscribeSafe(_observer1)); + _observer2.SetResource(source2.SubscribeSafe(_observer2)); + _observer3.SetResource(source3.SubscribeSafe(_observer3)); + _observer4.SetResource(source4.SubscribeSafe(_observer4)); + _observer5.SetResource(source5.SubscribeSafe(_observer5)); + _observer6.SetResource(source6.SubscribeSafe(_observer6)); + _observer7.SetResource(source7.SubscribeSafe(_observer7)); + _observer8.SetResource(source8.SubscribeSafe(_observer8)); + _observer9.SetResource(source9.SubscribeSafe(_observer9)); + _observer10.SetResource(source10.SubscribeSafe(_observer10)); + _observer11.SetResource(source11.SubscribeSafe(_observer11)); + _observer12.SetResource(source12.SubscribeSafe(_observer12)); + _observer13.SetResource(source13.SubscribeSafe(_observer13)); + _observer14.SetResource(source14.SubscribeSafe(_observer14)); + _observer15.SetResource(source15.SubscribeSafe(_observer15)); + _observer16.SetResource(source16.SubscribeSafe(_observer16)); + + SetUpstream(StableCompositeDisposable.CreateTrusted(disposables)); + } + + protected override TResult GetResult() => _resultSelector(_observer1.Values.Dequeue(), _observer2.Values.Dequeue(), _observer3.Values.Dequeue(), _observer4.Values.Dequeue(), _observer5.Values.Dequeue(), _observer6.Values.Dequeue(), _observer7.Values.Dequeue(), _observer8.Values.Dequeue(), _observer9.Values.Dequeue(), _observer10.Values.Dequeue(), _observer11.Values.Dequeue(), _observer12.Values.Dequeue(), _observer13.Values.Dequeue(), _observer14.Values.Dequeue(), _observer15.Values.Dequeue(), _observer16.Values.Dequeue()); + } + } + + + #endregion + + #endregion +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Zip.NAry.tt b/LibExternal/System.Reactive/Linq/Observable/Zip.NAry.tt new file mode 100644 index 0000000..ddcd1fc --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Zip.NAry.tt @@ -0,0 +1,122 @@ +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; + +namespace System.Reactive.Linq.ObservableImpl +{ + #region [3,16]-ary + + /* The following code is generated by a T4 template. */ + + #region Zip auto-generated code (<#=DateTime.Now.ToString()#>) + +<# +for (var i = 3; i <= 16; i++) +{ + var ts = string.Join(", ", Enumerable.Range(1, i).Select(j => "T" + j)); + var os = string.Join(", ", Enumerable.Range(1, i).Select(j => "IObservable source" + j)); + var vs = string.Join(", ", Enumerable.Range(1, i).Select(j => "_observer" + j + ".Values.Dequeue()")); + var ss = string.Join(", ", Enumerable.Range(1, i).Select(j => "_source" + j)); +#> + internal sealed class Zip<<#=ts#>, TResult> : Producer, TResult>._> + { +<# +for (var j = 1; j <= i; j++) +{ +#> + private readonly IObservable> _source<#=j#>; +<# +} +#> + private readonly Func<<#=ts#>, TResult> _resultSelector; + + public Zip(<#=os#>, Func<<#=ts#>, TResult> resultSelector) + { +<# +for (var j = 1; j <= i; j++) +{ +#> + _source<#=j#> = source<#=j#>; +<# +} +#> + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new _(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(<#=ss#>); + + internal sealed class _ : ZipSink + { + private readonly Func<<#=ts#>, TResult> _resultSelector; + +<# +for (var j = 1; j <= i; j++) +{ +#> + private readonly ZipObserver> _observer<#=j#>; +<# +} +#> + + public _(Func<<#=ts#>, TResult> resultSelector, IObserver observer) + : base(<#=i#>, observer) + { + _resultSelector = resultSelector; + +<# +for (var j = 1; j <= i; j++) +{ +#> + _observer<#=j#> = new ZipObserver>(_gate, this, <#=j - 1#>); +<# +} +#> + } + + public void Run(<#=os#>) + { + var disposables = new IDisposable[<#=i#>]; + +<# +for (var j = 1; j <= i; j++) +{ +#> + disposables[<#=j - 1#>] = _observer<#=j#>; + Queues[<#= j - 1#>] = _observer<#=j#>.Values; + +<# +} + +for (var j = 1; j <= i; j++) +{ +#> + _observer<#=j#>.SetResource(source<#=j#>.SubscribeSafe(_observer<#=j#>)); +<# +} +#> + + SetUpstream(StableCompositeDisposable.CreateTrusted(disposables)); + } + + protected override TResult GetResult() => _resultSelector(<#=vs#>); + } + } + +<# +} +#> + + #endregion + + #endregion +} diff --git a/LibExternal/System.Reactive/Linq/Observable/Zip.cs b/LibExternal/System.Reactive/Linq/Observable/Zip.cs new file mode 100644 index 0000000..1e1b88b --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable/Zip.cs @@ -0,0 +1,744 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Disposables; +using System.Threading; + +namespace System.Reactive.Linq.ObservableImpl +{ + #region Binary + + internal static class Zip + { + internal sealed class Observable : Producer + { + private readonly IObservable _first; + private readonly IObservable _second; + private readonly Func _resultSelector; + + public Observable(IObservable first, IObservable second, Func resultSelector) + { + _first = first; + _second = second; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_first, _second); + + internal sealed class _ : IdentitySink + { + private readonly Func _resultSelector; + + private readonly object _gate; + + private readonly FirstObserver _firstObserver; + private SingleAssignmentDisposableValue _firstDisposable; + + private readonly SecondObserver _secondObserver; + private SingleAssignmentDisposableValue _secondDisposable; + + public _(Func resultSelector, IObserver observer) + : base(observer) + { + _gate = new object(); + + _firstObserver = new FirstObserver(this); + _secondObserver = new SecondObserver(this); + + _firstObserver.SetOther(_secondObserver); + _secondObserver.SetOther(_firstObserver); + + _resultSelector = resultSelector; + } + + public void Run(IObservable first, IObservable second) + { + _firstDisposable.Disposable = first.SubscribeSafe(_firstObserver); + _secondDisposable.Disposable = second.SubscribeSafe(_secondObserver); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _firstDisposable.Dispose(); + _secondDisposable.Dispose(); + + // clearing the queue should happen under the lock + // as they are plain Queues, not concurrent queues. + lock (_gate) + { + _firstObserver.Dispose(); + _secondObserver.Dispose(); + } + } + + base.Dispose(disposing); + } + + private sealed class FirstObserver : IObserver, IDisposable + { + private readonly _ _parent; + private readonly Queue _queue; + private SecondObserver _other; + + public FirstObserver(_ parent) + { + _parent = parent; + _queue = new Queue(); + _other = default!; // NB: Will be set by SetOther. + } + + public void SetOther(SecondObserver other) { _other = other; } + + public Queue Queue => _queue; + public bool Done { get; private set; } + + public void OnNext(TFirst value) + { + lock (_parent._gate) + { + if (_other.Queue.Count > 0) + { + var r = _other.Queue.Dequeue(); + + TResult res; + try + { + res = _parent._resultSelector(value, r); + } + catch (Exception ex) + { + _parent.ForwardOnError(ex); + return; + } + + _parent.ForwardOnNext(res); + } + else + { + if (_other.Done) + { + _parent.ForwardOnCompleted(); + return; + } + + _queue.Enqueue(value); + } + } + } + + public void OnError(Exception error) + { + lock (_parent._gate) + { + _parent.ForwardOnError(error); + } + } + + public void OnCompleted() + { + lock (_parent._gate) + { + Done = true; + + if (_other.Done) + { + _parent.ForwardOnCompleted(); + } + else + { + _parent._firstDisposable.Dispose(); + } + } + } + + public void Dispose() + { + _queue.Clear(); + } + } + + private sealed class SecondObserver : IObserver, IDisposable + { + private readonly _ _parent; + private readonly Queue _queue; + private FirstObserver _other; + + public SecondObserver(_ parent) + { + _parent = parent; + _queue = new Queue(); + _other = default!; // NB: Will be set by SetOther. + } + + public void SetOther(FirstObserver other) { _other = other; } + + public Queue Queue => _queue; + public bool Done { get; private set; } + + public void OnNext(TSecond value) + { + lock (_parent._gate) + { + if (_other.Queue.Count > 0) + { + var l = _other.Queue.Dequeue(); + + TResult res; + try + { + res = _parent._resultSelector(l, value); + } + catch (Exception ex) + { + _parent.ForwardOnError(ex); + return; + } + + _parent.ForwardOnNext(res); + } + else + { + if (_other.Done) + { + _parent.ForwardOnCompleted(); + return; + } + + _queue.Enqueue(value); + } + } + } + + public void OnError(Exception error) + { + lock (_parent._gate) + { + _parent.ForwardOnError(error); + } + } + + public void OnCompleted() + { + lock (_parent._gate) + { + Done = true; + + if (_other.Done) + { + _parent.ForwardOnCompleted(); + } + else + { + _parent._secondDisposable.Dispose(); + } + } + } + + public void Dispose() + { + _queue.Clear(); + } + } + } + } + + internal sealed class Enumerable : Producer + { + private readonly IObservable _first; + private readonly IEnumerable _second; + private readonly Func _resultSelector; + + public Enumerable(IObservable first, IEnumerable second, Func resultSelector) + { + _first = first; + _second = second; + _resultSelector = resultSelector; + } + + protected override _ CreateSink(IObserver observer) => new(_resultSelector, observer); + + protected override void Run(_ sink) => sink.Run(_first, _second); + + internal sealed class _ : Sink + { + private readonly Func _resultSelector; + + public _(Func resultSelector, IObserver observer) + : base(observer) + { + _resultSelector = resultSelector; + } + + private int _enumerationInProgress; + + private IEnumerator? _rightEnumerator; + + private static readonly IEnumerator DisposedEnumerator = MakeDisposedEnumerator(); + + private static IEnumerator MakeDisposedEnumerator() + { + yield break; + } + + public void Run(IObservable first, IEnumerable second) + { + // + // Notice the evaluation order of obtaining the enumerator and subscribing to the + // observable sequence is reversed compared to the operator's signature. This is + // required to make sure the enumerator is available as soon as the observer can + // be called. Otherwise, we end up having a race for the initialization and use + // of the _rightEnumerator field. + // + try + { + var enumerator = second.GetEnumerator(); + if (Interlocked.CompareExchange(ref _rightEnumerator, enumerator, null) != null) + { + enumerator.Dispose(); + return; + } + } + catch (Exception exception) + { + ForwardOnError(exception); + + return; + } + + Run(first); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (Interlocked.Increment(ref _enumerationInProgress) == 1) + { + Interlocked.Exchange(ref _rightEnumerator, DisposedEnumerator)?.Dispose(); + } + } + + base.Dispose(disposing); + } + + public override void OnNext(TFirst value) + { + var currentEnumerator = Volatile.Read(ref _rightEnumerator); + + if (currentEnumerator == DisposedEnumerator) + { + return; + } + + if (Interlocked.Increment(ref _enumerationInProgress) != 1) + { + return; + } + + bool hasNext; + TSecond? right = default; + var wasDisposed = false; + + try + { + try + { + hasNext = currentEnumerator!.MoveNext(); + + if (hasNext) + { + right = currentEnumerator.Current; + } + } + finally + { + if (Interlocked.Decrement(ref _enumerationInProgress) != 0) + { + Interlocked.Exchange(ref _rightEnumerator, DisposedEnumerator)?.Dispose(); + wasDisposed = true; + } + } + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + if (wasDisposed) + { + return; + } + + if (hasNext) + { + TResult result; + try + { + result = _resultSelector(value, right!); // NB: Not null when hasNext is true. + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + ForwardOnNext(result); + } + else + { + ForwardOnCompleted(); + } + } + } + } + } + + #endregion + + #region [3,16]-ary + + #region Helpers for n-ary overloads + + internal interface IZip + { + void Next(int index); + void Fail(Exception error); + void Done(int index); + } + + internal abstract class ZipSink : IdentitySink, IZip + { + protected readonly object _gate; + + private readonly ICollection[] _queues; + private readonly bool[] _isDone; + + protected ZipSink(int arity, IObserver observer) + : base(observer) + { + _gate = new object(); + + _isDone = new bool[arity]; + _queues = new ICollection[arity]; + } + + public ICollection[] Queues => _queues; + + public void Next(int index) + { + var hasValueAll = true; + foreach (var queue in _queues) + { + if (queue.Count == 0) + { + hasValueAll = false; + break; + } + } + + if (hasValueAll) + { + TResult res; + try + { + res = GetResult(); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + ForwardOnNext(res); + } + else + { + var allOthersDone = true; + for (var i = 0; i < _isDone.Length; i++) + { + if (i != index && !_isDone[i]) + { + allOthersDone = false; + break; + } + } + + if (allOthersDone) + { + ForwardOnCompleted(); + } + } + } + + protected abstract TResult GetResult(); + + public void Fail(Exception error) + { + ForwardOnError(error); + } + + public void Done(int index) + { + _isDone[index] = true; + + var allDone = true; + foreach (var isDone in _isDone) + { + if (!isDone) + { + allDone = false; + break; + } + } + + if (allDone) + { + ForwardOnCompleted(); + } + } + } + + internal sealed class ZipObserver : SafeObserver + { + private readonly object _gate; + private readonly IZip _parent; + private readonly int _index; + private readonly Queue _values; + + public ZipObserver(object gate, IZip parent, int index) + { + _gate = gate; + _parent = parent; + _index = index; + _values = new Queue(); + } + + public Queue Values => _values; + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + lock (_gate) + { + _values.Clear(); + } + } + } + + public override void OnNext(T value) + { + lock (_gate) + { + _values.Enqueue(value); + _parent.Next(_index); + } + } + + public override void OnError(Exception error) + { + Dispose(); + + lock (_gate) + { + _parent.Fail(error); + } + } + + public override void OnCompleted() + { + // Calling Dispose() here would clear the queue prematurely. + // We only need to dispose the IDisposable of the upstream, + // which is done by SafeObserver.Dispose(bool). + base.Dispose(true); + + lock (_gate) + { + _parent.Done(_index); + } + } + } + + #endregion + + #endregion + + #region N-ary + + internal sealed class Zip : Producer, Zip._> + { + private readonly IEnumerable> _sources; + + public Zip(IEnumerable> sources) + { + _sources = sources; + } + + protected override _ CreateSink(IObserver> observer) => new(observer); + + protected override void Run(_ sink) => sink.Run(_sources); + + internal sealed class _ : IdentitySink> + { + private readonly object _gate; + + public _(IObserver> observer) + : base(observer) + { + _gate = new object(); + + // NB: These will be set in Run before getting used. + _queues = null!; + _isDone = null!; + } + + private Queue[] _queues; + private bool[] _isDone; + private SingleAssignmentDisposableValue[]? _subscriptions; + + public void Run(IEnumerable> sources) + { + var srcs = sources.ToArray(); + + var N = srcs.Length; + + _queues = new Queue[N]; + for (var i = 0; i < N; i++) + { + _queues[i] = new Queue(); + } + + _isDone = new bool[N]; + + var subscriptions = new SingleAssignmentDisposableValue[N]; + + if (Interlocked.CompareExchange(ref _subscriptions, subscriptions, null) == null) + { + for (var i = 0; i < N; i++) + { + var o = new SourceObserver(this, i); + subscriptions[i].Disposable = srcs[i].SubscribeSafe(o); + } + } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + var subscriptions = Interlocked.Exchange(ref _subscriptions, Array.Empty()); + if (subscriptions != null && subscriptions != Array.Empty()) + { + for (var i = 0; i < subscriptions.Length; i++) + { + subscriptions[i].Dispose(); + } + + lock (_gate) + { + foreach (var q in _queues) + { + q.Clear(); + } + } + } + } + base.Dispose(disposing); + } + + private void OnNext(int index, TSource value) + { + lock (_gate) + { + _queues[index].Enqueue(value); + + if (_queues.All(q => q.Count > 0)) + { + var n = _queues.Length; + + var res = new List(n); + for (var i = 0; i < n; i++) + { + res.Add(_queues[i].Dequeue()); + } + + ForwardOnNext(res); + } + else if (_isDone.AllExcept(index)) + { + ForwardOnCompleted(); + } + } + } + + private new void OnError(Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + } + + private void OnCompleted(int index) + { + lock (_gate) + { + _isDone[index] = true; + + if (_isDone.All()) + { + ForwardOnCompleted(); + } + else + { + var subscriptions = Volatile.Read(ref _subscriptions); + if (subscriptions != null && subscriptions != Array.Empty()) + { + subscriptions[index].Dispose(); + } + } + } + } + + private sealed class SourceObserver : IObserver + { + private readonly _ _parent; + private readonly int _index; + + public SourceObserver(_ parent, int index) + { + _parent = parent; + _index = index; + } + + public void OnNext(TSource value) + { + _parent.OnNext(_index, value); + } + + public void OnError(Exception error) + { + _parent.OnError(error); + } + + public void OnCompleted() + { + _parent.OnCompleted(_index); + } + } + } + } + + #endregion +} diff --git a/LibExternal/System.Reactive/Linq/ObservableEx.cs b/LibExternal/System.Reactive/Linq/ObservableEx.cs new file mode 100644 index 0000000..c9b47ad --- /dev/null +++ b/LibExternal/System.Reactive/Linq/ObservableEx.cs @@ -0,0 +1,290 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Concurrency; + +namespace System.Reactive.Linq +{ + /// + /// Provides a set of static methods for writing in-memory queries over observable sequences. + /// +#pragma warning disable CA1711 // (Don't use Ex suffix.) This has been a public type for many years, so we can't rename it now. + public static partial class ObservableEx +#pragma warning restore CA1711 + { +#pragma warning disable IDE1006 // Naming Styles: 3rd party code is known to reflect for this specific field name +#pragma warning disable IDE0044 // Make readonly: since 3rd party code reflects for this, we shouldn't pretend it won't change + private static IQueryLanguageEx s_impl = QueryServices.GetQueryImpl(new QueryLanguageEx()); +#pragma warning restore IDE1006, IDE0044 // Naming Styles, Make readonly + + #region Create + + /// + /// Subscribes to each observable sequence returned by the iteratorMethod in sequence and returns the observable sequence of values sent to the observer given to the iteratorMethod. + /// + /// The type of the elements in the produced sequence. + /// Iterator method that produces elements in the resulting sequence by calling the given observer. + /// An observable sequence obtained by running the iterator and returning the elements that were sent to the observer. + /// is null. + [Experimental] + public static IObservable Create(Func, IEnumerable>> iteratorMethod) + { + if (iteratorMethod == null) + { + throw new ArgumentNullException(nameof(iteratorMethod)); + } + + return s_impl.Create(iteratorMethod); + } + + /// + /// Subscribes to each observable sequence returned by the iteratorMethod in sequence and produces a Unit value on the resulting sequence for each step of the iteration. + /// + /// Iterator method that drives the resulting observable sequence. + /// An observable sequence obtained by running the iterator and returning Unit values for each iteration step. + /// is null. + [Experimental] + public static IObservable Create(Func>> iteratorMethod) + { + if (iteratorMethod == null) + { + throw new ArgumentNullException(nameof(iteratorMethod)); + } + + return s_impl.Create(iteratorMethod); + } + + #endregion + + #region Expand + + /// + /// Expands an observable sequence by recursively invoking selector, using the specified scheduler to enumerate the queue of obtained sequences. + /// + /// The type of the elements in the source sequence and each of the recursively expanded sources obtained by running the selector function. + /// Source sequence with the initial elements. + /// Selector function to invoke for each produced element, resulting in another sequence to which the selector will be invoked recursively again. + /// Scheduler on which to perform the expansion by enumerating the internal queue of obtained sequences. + /// An observable sequence containing all the elements produced by the recursive expansion. + /// or or is null. + [Experimental] + public static IObservable Expand(this IObservable source, Func> selector, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.Expand(source, selector, scheduler); + } + + /// + /// Expands an observable sequence by recursively invoking selector. + /// + /// The type of the elements in the source sequence and each of the recursively expanded sources obtained by running the selector function. + /// Source sequence with the initial elements. + /// Selector function to invoke for each produced element, resulting in another sequence to which the selector will be invoked recursively again. + /// An observable sequence containing all the elements produced by the recursive expansion. + /// or is null. + [Experimental] + public static IObservable Expand(this IObservable source, Func> selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Expand(source, selector); + } + + #endregion + + #region ForkJoin + + /// + /// Runs two observable sequences in parallel and combines their last elements. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable sequence. + /// Second observable sequence. + /// Result selector function to invoke with the last elements of both sequences. + /// An observable sequence with the result of calling the selector function with the last elements of both input sequences. + /// or or is null. + [Experimental] + public static IObservable ForkJoin(this IObservable first, IObservable second, Func resultSelector) + { + if (first == null) + { + throw new ArgumentNullException(nameof(first)); + } + + if (second == null) + { + throw new ArgumentNullException(nameof(second)); + } + + if (resultSelector == null) + { + throw new ArgumentNullException(nameof(resultSelector)); + } + + return s_impl.ForkJoin(first, second, resultSelector); + } + + /// + /// Runs all specified observable sequences in parallel and collects their last elements. + /// + /// The type of the elements in the source sequences. + /// Observable sequence to collect the last elements for. + /// An observable sequence with an array collecting the last elements of all the input sequences. + /// is null. + [Experimental] + public static IObservable ForkJoin(params IObservable[] sources) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + return s_impl.ForkJoin(sources); + } + + /// + /// Runs all observable sequences in the enumerable sources sequence in parallel and collect their last elements. + /// + /// The type of the elements in the source sequences. + /// Observable sequence to collect the last elements for. + /// An observable sequence with an array collecting the last elements of all the input sequences. + /// is null. + [Experimental] + public static IObservable ForkJoin(this IEnumerable> sources) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + return s_impl.ForkJoin(sources); + } + + #endregion + + #region Let + + /// + /// Returns an observable sequence that is the result of invoking the selector on the source sequence, without sharing subscriptions. + /// This operator allows for a fluent style of writing queries that use the same sequence multiple times. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence that will be shared in the selector function. + /// Selector function which can use the source sequence as many times as needed, without sharing subscriptions to the source sequence. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// or is null. + [Experimental] + public static IObservable Let(this IObservable source, Func, IObservable> selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.Let(source, selector); + } + + #endregion + + #region ManySelect + + /// + /// Comonadic bind operator. + /// + [Experimental] + public static IObservable ManySelect(this IObservable source, Func, TResult> selector, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return s_impl.ManySelect(source, selector, scheduler); + } + + /// + /// Comonadic bind operator. + /// + [Experimental] + public static IObservable ManySelect(this IObservable source, Func, TResult> selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return s_impl.ManySelect(source, selector); + } + + #endregion + + #region ToListObservable + + /// + /// Immediately subscribes to source and retains the elements in the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Object that's both an observable sequence and a list which can be used to access the source sequence's elements. + /// is null. + [Experimental] + public static ListObservable ToListObservable(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return s_impl.ToListObservable(source); + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/Observable_.cs b/LibExternal/System.Reactive/Linq/Observable_.cs new file mode 100644 index 0000000..92ea76a --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Observable_.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq +{ + /// + /// Provides a set of static methods for writing in-memory queries over observable sequences. + /// + public static partial class Observable + { +#pragma warning disable IDE1006 // Naming Styles: 3rd party code is known to reflect for this specific field name +#pragma warning disable IDE0044 // Make readonly: since 3rd party code reflects for this, we shouldn't pretend it won't change + private static IQueryLanguage s_impl = QueryServices.GetQueryImpl(new QueryLanguage()); +#pragma warning restore IDE1006, IDE0044 // Naming Styles, Make readonly + } +} diff --git a/LibExternal/System.Reactive/Linq/Qbservable.Generated.cs b/LibExternal/System.Reactive/Linq/Qbservable.Generated.cs new file mode 100644 index 0000000..b9df397 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Qbservable.Generated.cs @@ -0,0 +1,21652 @@ +/* + * WARNING: Auto-generated file (merged on 04/19/2023) + * Run Rx's auto-homoiconizer tool to generate this file (in the HomoIcon directory). + */ +#nullable enable +#pragma warning disable 1591 + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Linq.Expressions; +using System.Reactive.Concurrency; +using System.Reactive.Subjects; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Reactive.Linq +{ + [ExcludeFromCodeCoverage] + public static partial class Qbservable + { + /// + /// Applies an accumulator function over an observable sequence, returning the result of the aggregation as a single element in the result sequence. + /// For aggregation behavior with incremental intermediate results, see . + /// + /// The type of the elements in the source sequence and the result of the aggregation. + /// An observable sequence to aggregate over. + /// An accumulator function to be invoked on each element. + /// An observable sequence containing a single element with the final accumulator value. + /// + /// or is null. + /// (Asynchronous) The source sequence is empty. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Aggregate(this IQbservable source, Expression> accumulator) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (accumulator == null) + throw new ArgumentNullException(nameof(accumulator)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + accumulator + ) + ); + } + + /// + /// Applies an accumulator function over an observable sequence, returning the result of the aggregation as a single element in the result sequence. The specified seed value is used as the initial accumulator value. + /// For aggregation behavior with incremental intermediate results, see . + /// + /// The type of the elements in the source sequence. + /// The type of the result of the aggregation. + /// An observable sequence to aggregate over. + /// The initial accumulator value. + /// An accumulator function to be invoked on each element. + /// An observable sequence containing a single element with the final accumulator value. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Aggregate(this IQbservable source, TAccumulate seed, Expression> accumulator) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (accumulator == null) + throw new ArgumentNullException(nameof(accumulator)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TAccumulate)), + source.Expression, + Expression.Constant(seed, typeof(TAccumulate)), + accumulator + ) + ); + } + + /// + /// Applies an accumulator function over an observable sequence, returning the result of the aggregation as a single element in the result sequence. The specified seed value is used as the initial accumulator value, + /// and the specified result selector function is used to select the result value. + /// + /// The type of the elements in the source sequence. + /// The type of the accumulator value. + /// The type of the resulting value. + /// An observable sequence to aggregate over. + /// The initial accumulator value. + /// An accumulator function to be invoked on each element. + /// A function to transform the final accumulator value into the result value. + /// An observable sequence containing a single element with the final accumulator value. + /// + /// or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Aggregate(this IQbservable source, TAccumulate seed, Expression> accumulator, Expression> resultSelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (accumulator == null) + throw new ArgumentNullException(nameof(accumulator)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TAccumulate), typeof(TResult)), + source.Expression, + Expression.Constant(seed, typeof(TAccumulate)), + accumulator, + resultSelector + ) + ); + } + + /// + /// Determines whether all elements of an observable sequence satisfy a condition. + /// + /// The type of the elements in the source sequence. + /// An observable sequence whose elements to apply the predicate to. + /// A function to test each element for a condition. + /// An observable sequence containing a single element determining whether all elements in the source sequence pass the test in the specified predicate. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable All(this IQbservable source, Expression> predicate) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (predicate == null) + throw new ArgumentNullException(nameof(predicate)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + predicate + ) + ); + } + + /// + /// Propagates the observable sequence that reacts first. + /// + /// The type of the elements in the source sequences. + /// First observable sequence. + /// Second observable sequence. + /// An observable sequence that surfaces either of the given sequences, whichever reacted first. + /// + /// or is null. + public static IQbservable Amb(this IQbservable first, IObservable second) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + + return first.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + first.Expression, + GetSourceExpression(second) + ) + ); + } + + /// + /// Propagates the observable sequence that reacts first. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequences. + /// Observable sources competing to react first. + /// An observable sequence that surfaces any of the given sequences, whichever reacted first. + /// + /// is null. + public static IQbservable Amb(this IQbservableProvider provider, params IObservable[] sources) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + GetSourceExpression(sources) + ) + ); + } + + /// + /// Propagates the observable sequence that reacts first. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequences. + /// Observable sources competing to react first. + /// An observable sequence that surfaces any of the given sequences, whichever reacted first. + /// + /// is null. + public static IQbservable Amb(this IQbservableProvider provider, IEnumerable> sources) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + GetSourceExpression(sources) + ) + ); + } + + /// + /// Determines whether an observable sequence contains any elements. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to check for non-emptiness. + /// An observable sequence containing a single element determining whether the source sequence contains any elements. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Any(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Determines whether any element of an observable sequence satisfies a condition. + /// + /// The type of the elements in the source sequence. + /// An observable sequence whose elements to apply the predicate to. + /// A function to test each element for a condition. + /// An observable sequence containing a single element determining whether any elements in the source sequence pass the test in the specified predicate. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Any(this IQbservable source, Expression> predicate) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (predicate == null) + throw new ArgumentNullException(nameof(predicate)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + predicate + ) + ); + } + + /// + /// Append a value to an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to append the value to. + /// Value to append to the specified sequence. + /// The source sequence appended with the specified value. + /// + /// is null. + public static IQbservable Append(this IQbservable source, TSource value) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(value, typeof(TSource)) + ) + ); + } + + /// + /// Append a value to an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to append the value to. + /// Value to append to the specified sequence. + /// Scheduler to emit the append values on. + /// The source sequence appended with the specified value. + /// + /// is null. + public static IQbservable Append(this IQbservable source, TSource value, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(value, typeof(TSource)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Automatically connect the upstream IConnectableObservable at most once when the + /// specified number of IObservers have subscribed to this IObservable. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequence. + /// Connectable observable sequence. + /// The number of observers required to subscribe before the connection to source happens, non-positive value will trigger an immediate subscription. + /// If not null, the connection's IDisposable is provided to it. + /// An observable sequence that connects to the source at most once when the given number of observers have subscribed to it. + /// + /// is null. + public static IQbservable AutoConnect(this IQbservableProvider provider, IConnectableObservable source, int minObservers, Expression> onConnect) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (onConnect == null) + throw new ArgumentNullException(nameof(onConnect)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(source, typeof(IConnectableObservable)), + Expression.Constant(minObservers, typeof(int)), + onConnect + ) + ); + } + + /// + /// Computes the average of an observable sequence of values. + /// + /// A sequence of values to calculate the average of. + /// An observable sequence containing a single element with the average of the sequence of values. + /// + /// is null. + /// (Asynchronous) The source sequence is empty. + /// (Asynchronous) The sum of the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Average(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Computes the average of an observable sequence of values. + /// + /// A sequence of values to calculate the average of. + /// An observable sequence containing a single element with the average of the sequence of values. + /// + /// is null. + /// (Asynchronous) The source sequence is empty. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Average(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Computes the average of an observable sequence of values. + /// + /// A sequence of values to calculate the average of. + /// An observable sequence containing a single element with the average of the sequence of values. + /// + /// is null. + /// (Asynchronous) The source sequence is empty. + /// (Asynchronous) The sum of the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Average(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Computes the average of an observable sequence of values. + /// + /// A sequence of values to calculate the average of. + /// An observable sequence containing a single element with the average of the sequence of values. + /// + /// is null. + /// (Asynchronous) The source sequence is empty. + /// (Asynchronous) The sum of the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Average(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Computes the average of an observable sequence of nullable values. + /// + /// A sequence of nullable values to calculate the average of. + /// An observable sequence containing a single element with the average of the sequence of values, or null if the source sequence is empty or contains only values that are null. + /// + /// is null. + /// (Asynchronous) The source sequence is empty. + /// (Asynchronous) The sum of the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Average(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Computes the average of an observable sequence of nullable values. + /// + /// A sequence of nullable values to calculate the average of. + /// An observable sequence containing a single element with the average of the sequence of values, or null if the source sequence is empty or contains only values that are null. + /// + /// is null. + /// (Asynchronous) The source sequence is empty. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Average(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Computes the average of an observable sequence of nullable values. + /// + /// A sequence of nullable values to calculate the average of. + /// An observable sequence containing a single element with the average of the sequence of values, or null if the source sequence is empty or contains only values that are null. + /// + /// is null. + /// (Asynchronous) The source sequence is empty. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + /// (Asynchronous) The sum of the elements in the source sequence is larger than . + public static IQbservable Average(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Computes the average of an observable sequence of nullable values. + /// + /// A sequence of nullable values to calculate the average of. + /// An observable sequence containing a single element with the average of the sequence of values, or null if the source sequence is empty or contains only values that are null. + /// + /// is null. + /// (Asynchronous) The source sequence is empty. + /// (Asynchronous) The sum of the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Average(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Computes the average of an observable sequence of nullable values. + /// + /// A sequence of nullable values to calculate the average of. + /// An observable sequence containing a single element with the average of the sequence of values, or null if the source sequence is empty or contains only values that are null. + /// + /// is null. + /// (Asynchronous) The source sequence is empty. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Average(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Computes the average of an observable sequence of values. + /// + /// A sequence of values to calculate the average of. + /// An observable sequence containing a single element with the average of the sequence of values. + /// + /// is null. + /// (Asynchronous) The source sequence is empty. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Average(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Computes the average of an observable sequence of values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to calculate the average of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the average of the sequence of values. + /// + /// or is null. + /// (Asynchronous) The source sequence is empty. + /// (Asynchronous) The sum of the projected values for the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Average(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Computes the average of an observable sequence of values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to calculate the average of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the average of the sequence of values. + /// + /// or is null. + /// (Asynchronous) The source sequence is empty. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Average(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Computes the average of an observable sequence of values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to calculate the average of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the average of the sequence of values. + /// + /// or is null. + /// (Asynchronous) The source sequence is empty. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Average(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Computes the average of an observable sequence of values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to calculate the average of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the average of the sequence of values. + /// + /// or is null. + /// (Asynchronous) The source sequence is empty. + /// (Asynchronous) The sum of the projected values for the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Average(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Computes the average of an observable sequence of values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to calculate the average of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the average of the sequence of values. + /// + /// or is null. + /// (Asynchronous) The source sequence is empty. + /// (Asynchronous) The sum of the projected values for the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Average(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Computes the average of an observable sequence of nullable values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to calculate the average of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the average of the sequence of values, or null if the source sequence is empty or contains only values that are null. + /// + /// or is null. + /// (Asynchronous) The source sequence is empty. + /// (Asynchronous) The sum of the projected values for the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Average(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Computes the average of an observable sequence of nullable values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to calculate the average of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the average of the sequence of values, or null if the source sequence is empty or contains only values that are null. + /// + /// or is null. + /// (Asynchronous) The source sequence is empty. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Average(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Computes the average of an observable sequence of nullable values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to calculate the average of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the average of the sequence of values, or null if the source sequence is empty or contains only values that are null. + /// + /// or is null. + /// (Asynchronous) The source sequence is empty. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Average(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Computes the average of an observable sequence of nullable values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to calculate the average of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the average of the sequence of values, or null if the source sequence is empty or contains only values that are null. + /// + /// or is null. + /// (Asynchronous) The source sequence is empty. + /// (Asynchronous) The sum of the projected values for the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Average(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Computes the average of an observable sequence of nullable values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to calculate the average of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the average of the sequence of values, or null if the source sequence is empty or contains only values that are null. + /// + /// or is null. + /// (Asynchronous) The source sequence is empty. + /// (Asynchronous) The sum of the projected values for the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Average(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Projects each element of an observable sequence into consecutive non-overlapping buffers which are produced based on element count information. + /// + /// The type of the elements in the source sequence, and in the lists in the result sequence. + /// Source sequence to produce buffers over. + /// Length of each buffer. + /// An observable sequence of buffers. + /// + /// is null. + /// + /// is less than or equal to zero. + public static IQbservable> Buffer(this IQbservable source, int count) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(count, typeof(int)) + ) + ); + } + + /// + /// Projects each element of an observable sequence into zero or more buffers which are produced based on element count information. + /// + /// The type of the elements in the source sequence, and in the lists in the result sequence. + /// Source sequence to produce buffers over. + /// Length of each buffer. + /// Number of elements to skip between creation of consecutive buffers. + /// An observable sequence of buffers. + /// + /// is null. + /// + /// or is less than or equal to zero. + public static IQbservable> Buffer(this IQbservable source, int count, int skip) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(count, typeof(int)), + Expression.Constant(skip, typeof(int)) + ) + ); + } + + /// + /// Projects each element of an observable sequence into consecutive non-overlapping buffers which are produced based on timing information. + /// + /// The type of the elements in the source sequence, and in the lists in the result sequence. + /// Source sequence to produce buffers over. + /// Length of each buffer. + /// An observable sequence of buffers. + /// + /// is null. + /// + /// is less than TimeSpan.Zero. + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create buffers as fast as it can. + /// Because all source sequence elements end up in one of the buffers, some buffers won't have a zero time span. This is a side-effect of the asynchrony introduced + /// by the scheduler, where the action to close the current buffer and to create a new buffer may not execute immediately, despite the TimeSpan.Zero due time. + /// + public static IQbservable> Buffer(this IQbservable source, TimeSpan timeSpan) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(timeSpan, typeof(TimeSpan)) + ) + ); + } + + /// + /// Projects each element of an observable sequence into a buffer that's sent out when either it's full or a given amount of time has elapsed. + /// A useful real-world analogy of this overload is the behavior of a ferry leaving the dock when all seats are taken, or at the scheduled time of departure, whichever event occurs first. + /// + /// The type of the elements in the source sequence, and in the lists in the result sequence. + /// Source sequence to produce buffers over. + /// Maximum time length of a window. + /// Maximum element count of a window. + /// An observable sequence of buffers. + /// + /// is null. + /// + /// is less than TimeSpan.Zero. -or- is less than or equal to zero. + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create buffers as fast as it can. + /// Because all source sequence elements end up in one of the buffers, some buffers won't have a zero time span. This is a side-effect of the asynchrony introduced + /// by the scheduler, where the action to close the current buffer and to create a new buffer may not execute immediately, despite the TimeSpan.Zero due time. + /// + public static IQbservable> Buffer(this IQbservable source, TimeSpan timeSpan, int count) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(timeSpan, typeof(TimeSpan)), + Expression.Constant(count, typeof(int)) + ) + ); + } + + /// + /// Projects each element of an observable sequence into a buffer that's sent out when either it's full or a given amount of time has elapsed, using the specified scheduler to run timers. + /// A useful real-world analogy of this overload is the behavior of a ferry leaving the dock when all seats are taken, or at the scheduled time of departure, whichever event occurs first. + /// + /// The type of the elements in the source sequence, and in the lists in the result sequence. + /// Source sequence to produce buffers over. + /// Maximum time length of a buffer. + /// Maximum element count of a buffer. + /// Scheduler to run buffering timers on. + /// An observable sequence of buffers. + /// + /// or is null. + /// + /// is less than TimeSpan.Zero. -or- is less than or equal to zero. + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create buffers as fast as it can. + /// Because all source sequence elements end up in one of the buffers, some buffers won't have a zero time span. This is a side-effect of the asynchrony introduced + /// by the scheduler, where the action to close the current buffer and to create a new buffer may not execute immediately, despite the TimeSpan.Zero due time. + /// + public static IQbservable> Buffer(this IQbservable source, TimeSpan timeSpan, int count, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(timeSpan, typeof(TimeSpan)), + Expression.Constant(count, typeof(int)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Projects each element of an observable sequence into consecutive non-overlapping buffers which are produced based on timing information, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence, and in the lists in the result sequence. + /// Source sequence to produce buffers over. + /// Length of each buffer. + /// Scheduler to run buffering timers on. + /// An observable sequence of buffers. + /// + /// or is null. + /// + /// is less than TimeSpan.Zero. + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create buffers as fast as it can. + /// Because all source sequence elements end up in one of the buffers, some buffers won't have a zero time span. This is a side-effect of the asynchrony introduced + /// by the scheduler, where the action to close the current buffer and to create a new buffer may not execute immediately, despite the TimeSpan.Zero due time. + /// + public static IQbservable> Buffer(this IQbservable source, TimeSpan timeSpan, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(timeSpan, typeof(TimeSpan)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Projects each element of an observable sequence into zero or more buffers which are produced based on timing information. + /// + /// The type of the elements in the source sequence, and in the lists in the result sequence. + /// Source sequence to produce buffers over. + /// Length of each buffer. + /// Interval between creation of consecutive buffers. + /// An observable sequence of buffers. + /// + /// is null. + /// + /// or is less than TimeSpan.Zero. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create buffers with minimum duration + /// length. However, some buffers won't have a zero time span. This is a side-effect of the asynchrony introduced by the scheduler, where the action to close the + /// current buffer may not execute immediately, despite the TimeSpan.Zero due time. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create buffers as fast as it can. + /// However, this doesn't mean all buffers will start at the beginning of the source sequence. This is a side-effect of the asynchrony introduced by the scheduler, + /// where the action to create a new buffer may not execute immediately, despite the TimeSpan.Zero due time. + /// + /// + public static IQbservable> Buffer(this IQbservable source, TimeSpan timeSpan, TimeSpan timeShift) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(timeSpan, typeof(TimeSpan)), + Expression.Constant(timeShift, typeof(TimeSpan)) + ) + ); + } + + /// + /// Projects each element of an observable sequence into zero or more buffers which are produced based on timing information, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence, and in the lists in the result sequence. + /// Source sequence to produce buffers over. + /// Length of each buffer. + /// Interval between creation of consecutive buffers. + /// Scheduler to run buffering timers on. + /// An observable sequence of buffers. + /// + /// or is null. + /// + /// or is less than TimeSpan.Zero. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create buffers with minimum duration + /// length. However, some buffers won't have a zero time span. This is a side-effect of the asynchrony introduced by the scheduler, where the action to close the + /// current buffer may not execute immediately, despite the TimeSpan.Zero due time. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create buffers as fast as it can. + /// However, this doesn't mean all buffers will start at the beginning of the source sequence. This is a side-effect of the asynchrony introduced by the scheduler, + /// where the action to create a new buffer may not execute immediately, despite the TimeSpan.Zero due time. + /// + /// + public static IQbservable> Buffer(this IQbservable source, TimeSpan timeSpan, TimeSpan timeShift, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(timeSpan, typeof(TimeSpan)), + Expression.Constant(timeShift, typeof(TimeSpan)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Projects each element of an observable sequence into consecutive non-overlapping buffers. + /// + /// The type of the elements in the source sequence, and in the lists in the result sequence. + /// The type of the elements in the sequences indicating buffer boundary events. + /// Source sequence to produce buffers over. + /// Sequence of buffer boundary markers. The current buffer is closed and a new buffer is opened upon receiving a boundary marker. + /// An observable sequence of buffers. + /// + /// or is null. + public static IQbservable> Buffer(this IQbservable source, IObservable bufferBoundaries) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (bufferBoundaries == null) + throw new ArgumentNullException(nameof(bufferBoundaries)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TBufferBoundary)), + source.Expression, + GetSourceExpression(bufferBoundaries) + ) + ); + } + + /// + /// Projects each element of an observable sequence into consecutive non-overlapping buffers. + /// + /// The type of the elements in the source sequence, and in the lists in the result sequence. + /// The type of the elements in the sequences indicating buffer closing events. + /// Source sequence to produce buffers over. + /// A function invoked to define the boundaries of the produced buffers. A new buffer is started when the previous one is closed. + /// An observable sequence of buffers. + /// + /// or is null. + public static IQbservable> Buffer(this IQbservable source, Expression>> bufferClosingSelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (bufferClosingSelector == null) + throw new ArgumentNullException(nameof(bufferClosingSelector)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TBufferClosing)), + source.Expression, + bufferClosingSelector + ) + ); + } + + /// + /// Projects each element of an observable sequence into zero or more buffers. + /// + /// The type of the elements in the source sequence, and in the lists in the result sequence. + /// The type of the elements in the sequence indicating buffer opening events, also passed to the closing selector to obtain a sequence of buffer closing events. + /// The type of the elements in the sequences indicating buffer closing events. + /// Source sequence to produce buffers over. + /// Observable sequence whose elements denote the creation of new buffers. + /// A function invoked to define the closing of each produced buffer. + /// An observable sequence of buffers. + /// + /// or or is null. + public static IQbservable> Buffer(this IQbservable source, IObservable bufferOpenings, Expression>> bufferClosingSelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (bufferOpenings == null) + throw new ArgumentNullException(nameof(bufferOpenings)); + if (bufferClosingSelector == null) + throw new ArgumentNullException(nameof(bufferClosingSelector)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TBufferOpening), typeof(TBufferClosing)), + source.Expression, + GetSourceExpression(bufferOpenings), + bufferClosingSelector + ) + ); + } + + /// + /// Uses to determine which source in to return, choosing an empty sequence if no match is found. + /// + /// Query provider used to construct the data source. + /// The type of the value returned by the selector function, used to look up the resulting source. + /// The type of the elements in the result sequence. + /// Selector function invoked to determine the source to lookup in the dictionary. + /// Dictionary of sources to select from based on the invocation result. + /// The observable sequence retrieved from the dictionary based on the invocation result, or an empty sequence if no match is found. + /// + /// or is null. + public static IQbservable Case(this IQbservableProvider provider, Expression> selector, IDictionary> sources) + where TValue : notnull + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TValue), typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + selector, + Expression.Constant(sources, typeof(IDictionary>)) + ) + ); + } + + /// + /// Uses to determine which source in to return, choosing if no match is found. + /// + /// Query provider used to construct the data source. + /// The type of the value returned by the selector function, used to look up the resulting source. + /// The type of the elements in the result sequence. + /// Selector function invoked to determine the source to lookup in the dictionary. + /// Dictionary of sources to select from based on the invocation result. + /// Default source to select in case no matching source in is found. + /// The observable sequence retrieved from the dictionary based on the invocation result, or if no match is found. + /// + /// or or is null. + public static IQbservable Case(this IQbservableProvider provider, Expression> selector, IDictionary> sources, IObservable defaultSource) + where TValue : notnull + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + if (defaultSource == null) + throw new ArgumentNullException(nameof(defaultSource)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TValue), typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + selector, + Expression.Constant(sources, typeof(IDictionary>)), + GetSourceExpression(defaultSource) + ) + ); + } + + /// + /// Uses to determine which source in to return, choosing an empty sequence on the specified scheduler if no match is found. + /// + /// Query provider used to construct the data source. + /// The type of the value returned by the selector function, used to look up the resulting source. + /// The type of the elements in the result sequence. + /// Selector function invoked to determine the source to lookup in the dictionary. + /// Dictionary of sources to select from based on the invocation result. + /// Scheduler to generate an empty sequence on in case no matching source in is found. + /// The observable sequence retrieved from the dictionary based on the invocation result, or an empty sequence if no match is found. + /// + /// or or is null. + public static IQbservable Case(this IQbservableProvider provider, Expression> selector, IDictionary> sources, IScheduler scheduler) + where TValue : notnull + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TValue), typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + selector, + Expression.Constant(sources, typeof(IDictionary>)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Converts the elements of an observable sequence to the specified type. + /// + /// The type to convert the elements in the source sequence to. + /// The observable sequence that contains the elements to be converted. + /// An observable sequence that contains each element of the source sequence converted to the specified type. + /// + /// is null. + public static IQbservable Cast(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + source.Expression + ) + ); + } + + /// + /// Continues an observable sequence that is terminated by an exception with the next observable sequence. + /// + /// The type of the elements in the source sequence and handler sequence. + /// First observable sequence whose exception (if any) is caught. + /// Second observable sequence used to produce results when an error occurred in the first sequence. + /// An observable sequence containing the first sequence's elements, followed by the elements of the second sequence in case an exception occurred. + /// + /// or is null. + public static IQbservable Catch(this IQbservable first, IObservable second) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + + return first.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + first.Expression, + GetSourceExpression(second) + ) + ); + } + + /// + /// Continues an observable sequence that is terminated by an exception with the next observable sequence. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source and handler sequences. + /// Observable sequences to catch exceptions for. + /// An observable sequence containing elements from consecutive source sequences until a source sequence terminates successfully. + /// + /// is null. + public static IQbservable Catch(this IQbservableProvider provider, params IObservable[] sources) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + GetSourceExpression(sources) + ) + ); + } + + /// + /// Continues an observable sequence that is terminated by an exception with the next observable sequence. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source and handler sequences. + /// Observable sequences to catch exceptions for. + /// An observable sequence containing elements from consecutive source sequences until a source sequence terminates successfully. + /// + /// is null. + public static IQbservable Catch(this IQbservableProvider provider, IEnumerable> sources) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + GetSourceExpression(sources) + ) + ); + } + + /// + /// Continues an observable sequence that is terminated by an exception of the specified type with the observable sequence produced by the handler. + /// + /// The type of the elements in the source sequence and sequences returned by the exception handler function. + /// The type of the exception to catch and handle. Needs to derive from . + /// Source sequence. + /// Exception handler function, producing another observable sequence. + /// An observable sequence containing the source sequence's elements, followed by the elements produced by the handler's resulting observable sequence in case an exception occurred. + /// + /// or is null. + public static IQbservable Catch(this IQbservable source, Expression>> handler) + where TException : Exception + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (handler == null) + throw new ArgumentNullException(nameof(handler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TException)), + source.Expression, + handler + ) + ); + } + + /// + /// Produces an enumerable sequence of consecutive (possibly empty) chunks of the source sequence. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// The enumerable sequence that returns consecutive (possibly empty) chunks upon each iteration. + /// + /// is null. + /// This operator requires the source's object (see ) to implement . + public static IQueryable> Chunkify(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return ((IQueryProvider)source.Provider).CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Produces an enumerable sequence that returns elements collected/aggregated from the source sequence between consecutive iterations. + /// + /// The type of the elements in the source sequence. + /// The type of the elements produced by the merge operation during collection. + /// Source observable sequence. + /// Factory to create the initial collector object. + /// Merges a sequence element with the current collector. + /// Factory to replace the current collector by a new collector. + /// The enumerable sequence that returns collected/aggregated elements from the source sequence upon each iteration. + /// + /// or or or is null. + /// This operator requires the source's object (see ) to implement . + public static IQueryable Collect(this IQbservable source, Expression> getInitialCollector, Expression> merge, Expression> getNewCollector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (getInitialCollector == null) + throw new ArgumentNullException(nameof(getInitialCollector)); + if (merge == null) + throw new ArgumentNullException(nameof(merge)); + if (getNewCollector == null) + throw new ArgumentNullException(nameof(getNewCollector)); + + return ((IQueryProvider)source.Provider).CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + getInitialCollector, + merge, + getNewCollector + ) + ); + } + + /// + /// Produces an enumerable sequence that returns elements collected/aggregated from the source sequence between consecutive iterations. + /// + /// The type of the elements in the source sequence. + /// The type of the elements produced by the merge operation during collection. + /// Source observable sequence. + /// Factory to create a new collector object. + /// Merges a sequence element with the current collector. + /// The enumerable sequence that returns collected/aggregated elements from the source sequence upon each iteration. + /// + /// or or is null. + /// This operator requires the source's object (see ) to implement . + public static IQueryable Collect(this IQbservable source, Expression> newCollector, Expression> merge) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (newCollector == null) + throw new ArgumentNullException(nameof(newCollector)); + if (merge == null) + throw new ArgumentNullException(nameof(merge)); + + return ((IQueryProvider)source.Provider).CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + newCollector, + merge + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by emitting a list with the latest source elements whenever any of the observable sequences produces an element. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequences, and in the lists in the result sequence. + /// Observable sources. + /// An observable sequence containing lists of the latest elements of the sources. + /// + /// is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IQbservable> CombineLatest(this IQbservableProvider provider, IEnumerable> sources) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + GetSourceExpression(sources) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by emitting a list with the latest source elements whenever any of the observable sequences produces an element. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequences, and in the lists in the result sequence. + /// Observable sources. + /// An observable sequence containing lists of the latest elements of the sources. + /// + /// is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IQbservable> CombineLatest(this IQbservableProvider provider, params IObservable[] sources) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + GetSourceExpression(sources) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequences. + /// The type of the elements in the result sequence, returned by the selector function. + /// Observable sources. + /// Function to invoke whenever any of the sources produces an element. For efficiency, the input list is reused after the selector returns. Either aggregate or copy the values during the function call. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IQbservable CombineLatest(this IQbservableProvider provider, IEnumerable> sources, Expression, TResult>> resultSelector) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + GetSourceExpression(sources), + resultSelector + ) + ); + } + + /// + /// Merges two observable sequences into one observable sequence by using the selector function whenever one of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Function to invoke whenever either of the sources produces an element. + /// An observable sequence containing the result of combining elements of both sources using the specified result selector function. + /// + /// or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IQbservable CombineLatest(this IQbservable first, IObservable second, Expression> resultSelector) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return first.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TResult)), + first.Expression, + GetSourceExpression(second), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IQbservable CombineLatest(this IQbservable source1, IObservable source2, IObservable source3, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IQbservable CombineLatest(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IQbservable CombineLatest(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (source5 == null) + throw new ArgumentNullException(nameof(source5)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TSource5), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + GetSourceExpression(source5), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IQbservable CombineLatest(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (source5 == null) + throw new ArgumentNullException(nameof(source5)); + if (source6 == null) + throw new ArgumentNullException(nameof(source6)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TSource5), typeof(TSource6), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + GetSourceExpression(source5), + GetSourceExpression(source6), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IQbservable CombineLatest(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (source5 == null) + throw new ArgumentNullException(nameof(source5)); + if (source6 == null) + throw new ArgumentNullException(nameof(source6)); + if (source7 == null) + throw new ArgumentNullException(nameof(source7)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TSource5), typeof(TSource6), typeof(TSource7), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + GetSourceExpression(source5), + GetSourceExpression(source6), + GetSourceExpression(source7), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IQbservable CombineLatest(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (source5 == null) + throw new ArgumentNullException(nameof(source5)); + if (source6 == null) + throw new ArgumentNullException(nameof(source6)); + if (source7 == null) + throw new ArgumentNullException(nameof(source7)); + if (source8 == null) + throw new ArgumentNullException(nameof(source8)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TSource5), typeof(TSource6), typeof(TSource7), typeof(TSource8), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + GetSourceExpression(source5), + GetSourceExpression(source6), + GetSourceExpression(source7), + GetSourceExpression(source8), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or or or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IQbservable CombineLatest(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (source5 == null) + throw new ArgumentNullException(nameof(source5)); + if (source6 == null) + throw new ArgumentNullException(nameof(source6)); + if (source7 == null) + throw new ArgumentNullException(nameof(source7)); + if (source8 == null) + throw new ArgumentNullException(nameof(source8)); + if (source9 == null) + throw new ArgumentNullException(nameof(source9)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TSource5), typeof(TSource6), typeof(TSource7), typeof(TSource8), typeof(TSource9), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + GetSourceExpression(source5), + GetSourceExpression(source6), + GetSourceExpression(source7), + GetSourceExpression(source8), + GetSourceExpression(source9), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or or or or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IQbservable CombineLatest(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (source5 == null) + throw new ArgumentNullException(nameof(source5)); + if (source6 == null) + throw new ArgumentNullException(nameof(source6)); + if (source7 == null) + throw new ArgumentNullException(nameof(source7)); + if (source8 == null) + throw new ArgumentNullException(nameof(source8)); + if (source9 == null) + throw new ArgumentNullException(nameof(source9)); + if (source10 == null) + throw new ArgumentNullException(nameof(source10)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TSource5), typeof(TSource6), typeof(TSource7), typeof(TSource8), typeof(TSource9), typeof(TSource10), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + GetSourceExpression(source5), + GetSourceExpression(source6), + GetSourceExpression(source7), + GetSourceExpression(source8), + GetSourceExpression(source9), + GetSourceExpression(source10), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or or or or or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IQbservable CombineLatest(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (source5 == null) + throw new ArgumentNullException(nameof(source5)); + if (source6 == null) + throw new ArgumentNullException(nameof(source6)); + if (source7 == null) + throw new ArgumentNullException(nameof(source7)); + if (source8 == null) + throw new ArgumentNullException(nameof(source8)); + if (source9 == null) + throw new ArgumentNullException(nameof(source9)); + if (source10 == null) + throw new ArgumentNullException(nameof(source10)); + if (source11 == null) + throw new ArgumentNullException(nameof(source11)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TSource5), typeof(TSource6), typeof(TSource7), typeof(TSource8), typeof(TSource9), typeof(TSource10), typeof(TSource11), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + GetSourceExpression(source5), + GetSourceExpression(source6), + GetSourceExpression(source7), + GetSourceExpression(source8), + GetSourceExpression(source9), + GetSourceExpression(source10), + GetSourceExpression(source11), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or or or or or or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IQbservable CombineLatest(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (source5 == null) + throw new ArgumentNullException(nameof(source5)); + if (source6 == null) + throw new ArgumentNullException(nameof(source6)); + if (source7 == null) + throw new ArgumentNullException(nameof(source7)); + if (source8 == null) + throw new ArgumentNullException(nameof(source8)); + if (source9 == null) + throw new ArgumentNullException(nameof(source9)); + if (source10 == null) + throw new ArgumentNullException(nameof(source10)); + if (source11 == null) + throw new ArgumentNullException(nameof(source11)); + if (source12 == null) + throw new ArgumentNullException(nameof(source12)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TSource5), typeof(TSource6), typeof(TSource7), typeof(TSource8), typeof(TSource9), typeof(TSource10), typeof(TSource11), typeof(TSource12), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + GetSourceExpression(source5), + GetSourceExpression(source6), + GetSourceExpression(source7), + GetSourceExpression(source8), + GetSourceExpression(source9), + GetSourceExpression(source10), + GetSourceExpression(source11), + GetSourceExpression(source12), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Thirteenth observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or or or or or or or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IQbservable CombineLatest(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (source5 == null) + throw new ArgumentNullException(nameof(source5)); + if (source6 == null) + throw new ArgumentNullException(nameof(source6)); + if (source7 == null) + throw new ArgumentNullException(nameof(source7)); + if (source8 == null) + throw new ArgumentNullException(nameof(source8)); + if (source9 == null) + throw new ArgumentNullException(nameof(source9)); + if (source10 == null) + throw new ArgumentNullException(nameof(source10)); + if (source11 == null) + throw new ArgumentNullException(nameof(source11)); + if (source12 == null) + throw new ArgumentNullException(nameof(source12)); + if (source13 == null) + throw new ArgumentNullException(nameof(source13)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TSource5), typeof(TSource6), typeof(TSource7), typeof(TSource8), typeof(TSource9), typeof(TSource10), typeof(TSource11), typeof(TSource12), typeof(TSource13), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + GetSourceExpression(source5), + GetSourceExpression(source6), + GetSourceExpression(source7), + GetSourceExpression(source8), + GetSourceExpression(source9), + GetSourceExpression(source10), + GetSourceExpression(source11), + GetSourceExpression(source12), + GetSourceExpression(source13), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the fourteenth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Thirteenth observable source. + /// Fourteenth observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or or or or or or or or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IQbservable CombineLatest(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (source5 == null) + throw new ArgumentNullException(nameof(source5)); + if (source6 == null) + throw new ArgumentNullException(nameof(source6)); + if (source7 == null) + throw new ArgumentNullException(nameof(source7)); + if (source8 == null) + throw new ArgumentNullException(nameof(source8)); + if (source9 == null) + throw new ArgumentNullException(nameof(source9)); + if (source10 == null) + throw new ArgumentNullException(nameof(source10)); + if (source11 == null) + throw new ArgumentNullException(nameof(source11)); + if (source12 == null) + throw new ArgumentNullException(nameof(source12)); + if (source13 == null) + throw new ArgumentNullException(nameof(source13)); + if (source14 == null) + throw new ArgumentNullException(nameof(source14)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TSource5), typeof(TSource6), typeof(TSource7), typeof(TSource8), typeof(TSource9), typeof(TSource10), typeof(TSource11), typeof(TSource12), typeof(TSource13), typeof(TSource14), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + GetSourceExpression(source5), + GetSourceExpression(source6), + GetSourceExpression(source7), + GetSourceExpression(source8), + GetSourceExpression(source9), + GetSourceExpression(source10), + GetSourceExpression(source11), + GetSourceExpression(source12), + GetSourceExpression(source13), + GetSourceExpression(source14), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the fourteenth source sequence. + /// The type of the elements in the fifteenth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Thirteenth observable source. + /// Fourteenth observable source. + /// Fifteenth observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or or or or or or or or or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IQbservable CombineLatest(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, IObservable source15, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (source5 == null) + throw new ArgumentNullException(nameof(source5)); + if (source6 == null) + throw new ArgumentNullException(nameof(source6)); + if (source7 == null) + throw new ArgumentNullException(nameof(source7)); + if (source8 == null) + throw new ArgumentNullException(nameof(source8)); + if (source9 == null) + throw new ArgumentNullException(nameof(source9)); + if (source10 == null) + throw new ArgumentNullException(nameof(source10)); + if (source11 == null) + throw new ArgumentNullException(nameof(source11)); + if (source12 == null) + throw new ArgumentNullException(nameof(source12)); + if (source13 == null) + throw new ArgumentNullException(nameof(source13)); + if (source14 == null) + throw new ArgumentNullException(nameof(source14)); + if (source15 == null) + throw new ArgumentNullException(nameof(source15)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TSource5), typeof(TSource6), typeof(TSource7), typeof(TSource8), typeof(TSource9), typeof(TSource10), typeof(TSource11), typeof(TSource12), typeof(TSource13), typeof(TSource14), typeof(TSource15), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + GetSourceExpression(source5), + GetSourceExpression(source6), + GetSourceExpression(source7), + GetSourceExpression(source8), + GetSourceExpression(source9), + GetSourceExpression(source10), + GetSourceExpression(source11), + GetSourceExpression(source12), + GetSourceExpression(source13), + GetSourceExpression(source14), + GetSourceExpression(source15), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the fourteenth source sequence. + /// The type of the elements in the fifteenth source sequence. + /// The type of the elements in the sixteenth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Thirteenth observable source. + /// Fourteenth observable source. + /// Fifteenth observable source. + /// Sixteenth observable source. + /// Function to invoke whenever any of the sources produces an element. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or or or or or or or or or or or or or is null. + /// If a non-empty source completes, its very last value will be used for creating subsequent combinations until all sources terminate. + public static IQbservable CombineLatest(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, IObservable source15, IObservable source16, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (source5 == null) + throw new ArgumentNullException(nameof(source5)); + if (source6 == null) + throw new ArgumentNullException(nameof(source6)); + if (source7 == null) + throw new ArgumentNullException(nameof(source7)); + if (source8 == null) + throw new ArgumentNullException(nameof(source8)); + if (source9 == null) + throw new ArgumentNullException(nameof(source9)); + if (source10 == null) + throw new ArgumentNullException(nameof(source10)); + if (source11 == null) + throw new ArgumentNullException(nameof(source11)); + if (source12 == null) + throw new ArgumentNullException(nameof(source12)); + if (source13 == null) + throw new ArgumentNullException(nameof(source13)); + if (source14 == null) + throw new ArgumentNullException(nameof(source14)); + if (source15 == null) + throw new ArgumentNullException(nameof(source15)); + if (source16 == null) + throw new ArgumentNullException(nameof(source16)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TSource5), typeof(TSource6), typeof(TSource7), typeof(TSource8), typeof(TSource9), typeof(TSource10), typeof(TSource11), typeof(TSource12), typeof(TSource13), typeof(TSource14), typeof(TSource15), typeof(TSource16), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + GetSourceExpression(source5), + GetSourceExpression(source6), + GetSourceExpression(source7), + GetSourceExpression(source8), + GetSourceExpression(source9), + GetSourceExpression(source10), + GetSourceExpression(source11), + GetSourceExpression(source12), + GetSourceExpression(source13), + GetSourceExpression(source14), + GetSourceExpression(source15), + GetSourceExpression(source16), + resultSelector + ) + ); + } + + /// + /// Concatenates the second observable sequence to the first observable sequence upon successful termination of the first. + /// + /// The type of the elements in the source sequences. + /// First observable sequence. + /// Second observable sequence. + /// An observable sequence that contains the elements of the first sequence, followed by those of the second the sequence. + /// + /// or is null. + public static IQbservable Concat(this IQbservable first, IObservable second) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + + return first.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + first.Expression, + GetSourceExpression(second) + ) + ); + } + + /// + /// Concatenates all of the specified observable sequences, as long as the previous observable sequence terminated successfully. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequences. + /// Observable sequences to concatenate. + /// An observable sequence that contains the elements of each given sequence, in sequential order. + /// + /// is null. + public static IQbservable Concat(this IQbservableProvider provider, params IObservable[] sources) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + GetSourceExpression(sources) + ) + ); + } + + /// + /// Concatenates all observable sequences in the given enumerable sequence, as long as the previous observable sequence terminated successfully. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequences. + /// Observable sequences to concatenate. + /// An observable sequence that contains the elements of each given sequence, in sequential order. + /// + /// is null. + public static IQbservable Concat(this IQbservableProvider provider, IEnumerable> sources) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + GetSourceExpression(sources) + ) + ); + } + + /// + /// Concatenates all inner observable sequences, as long as the previous observable sequence terminated successfully. + /// + /// The type of the elements in the source sequences. + /// Observable sequence of inner observable sequences. + /// An observable sequence that contains the elements of each observed inner sequence, in sequential order. + /// + /// is null. + public static IQbservable Concat(this IQbservable> sources) + { + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return sources.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + sources.Expression + ) + ); + } + + /// + /// Concatenates all task results, as long as the previous task terminated successfully. + /// + /// The type of the results produced by the tasks. + /// Observable sequence of tasks. + /// An observable sequence that contains the results of each task, in sequential order. + /// + /// is null. + /// If the tasks support cancellation, consider manual conversion of the tasks using , followed by a concatenation operation using . + public static IQbservable Concat(this IQbservable> sources) + { + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return sources.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + sources.Expression + ) + ); + } + + /// + /// Determines whether an observable sequence contains a specified element by using the default equality comparer. + /// + /// The type of the elements in the source sequence. + /// An observable sequence in which to locate a value. + /// The value to locate in the source sequence. + /// An observable sequence containing a single element determining whether the source sequence contains an element that has the specified value. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Contains(this IQbservable source, TSource value) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(value, typeof(TSource)) + ) + ); + } + + /// + /// Determines whether an observable sequence contains a specified element by using a specified System.Collections.Generic.IEqualityComparer{T}. + /// + /// The type of the elements in the source sequence. + /// An observable sequence in which to locate a value. + /// The value to locate in the source sequence. + /// An equality comparer to compare elements. + /// An observable sequence containing a single element determining whether the source sequence contains an element that has the specified value. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Contains(this IQbservable source, TSource value, IEqualityComparer comparer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(value, typeof(TSource)), + Expression.Constant(comparer, typeof(IEqualityComparer)) + ) + ); + } + + /// + /// Returns an observable sequence containing an that represents the total number of elements in an observable sequence. + /// + /// The type of the elements in the source sequence. + /// An observable sequence that contains elements to be counted. + /// An observable sequence containing a single element with the number of elements in the input sequence. + /// + /// is null. + /// (Asynchronous) The number of elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Count(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Returns an observable sequence containing an that represents how many elements in the specified observable sequence satisfy a condition. + /// + /// The type of the elements in the source sequence. + /// An observable sequence that contains elements to be counted. + /// A function to test each element for a condition. + /// An observable sequence containing a single element with a number that represents how many elements in the input sequence satisfy the condition in the predicate function. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Count(this IQbservable source, Expression> predicate) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (predicate == null) + throw new ArgumentNullException(nameof(predicate)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + predicate + ) + ); + } + + /// + /// Creates an observable sequence from a specified Subscribe method implementation. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the produced sequence. + /// Implementation of the resulting observable sequence's Subscribe method. + /// The observable sequence with the specified implementation for the Subscribe method. + /// + /// is null. + /// + /// Use of this operator is preferred over manual implementation of the interface. In case + /// you need a type implementing rather than an anonymous implementation, consider using + /// the abstract base class. + /// + public static IQbservable Create(this IQbservableProvider provider, Expression, IDisposable>> subscribe) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (subscribe == null) + throw new ArgumentNullException(nameof(subscribe)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + subscribe + ) + ); + } + + /// + /// Creates an observable sequence from a specified Subscribe method implementation. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the produced sequence. + /// Implementation of the resulting observable sequence's Subscribe method, returning an Action delegate that will be wrapped in an IDisposable. + /// The observable sequence with the specified implementation for the Subscribe method. + /// + /// is null. + /// + /// Use of this operator is preferred over manual implementation of the interface. In case + /// you need a type implementing rather than an anonymous implementation, consider using + /// the abstract base class. + /// + public static IQbservable Create(this IQbservableProvider provider, Expression, Action>> subscribe) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (subscribe == null) + throw new ArgumentNullException(nameof(subscribe)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + subscribe + ) + ); + } + + /// + /// Creates an observable sequence from a specified cancellable asynchronous Subscribe method. + /// The CancellationToken passed to the asynchronous Subscribe method is tied to the returned disposable subscription, allowing best-effort cancellation. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the produced sequence. + /// Asynchronous method used to produce elements. + /// The observable sequence surfacing the elements produced by the asynchronous method. + /// + /// is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + /// When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous subscribe function will be signaled. + public static IQbservable Create(this IQbservableProvider provider, Expression, CancellationToken, Task>> subscribeAsync) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (subscribeAsync == null) + throw new ArgumentNullException(nameof(subscribeAsync)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + subscribeAsync + ) + ); + } + + /// + /// Creates an observable sequence from a specified asynchronous Subscribe method. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the produced sequence. + /// Asynchronous method used to produce elements. + /// The observable sequence surfacing the elements produced by the asynchronous method. + /// + /// is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + public static IQbservable Create(this IQbservableProvider provider, Expression, Task>> subscribeAsync) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (subscribeAsync == null) + throw new ArgumentNullException(nameof(subscribeAsync)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + subscribeAsync + ) + ); + } + + /// + /// Creates an observable sequence from a specified cancellable asynchronous Subscribe method. + /// The CancellationToken passed to the asynchronous Subscribe method is tied to the returned disposable subscription, allowing best-effort cancellation. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the produced sequence. + /// Asynchronous method used to implement the resulting sequence's Subscribe method. + /// The observable sequence with the specified implementation for the Subscribe method. + /// + /// is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + /// When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous subscribe function will be signaled. + public static IQbservable Create(this IQbservableProvider provider, Expression, CancellationToken, Task>> subscribeAsync) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (subscribeAsync == null) + throw new ArgumentNullException(nameof(subscribeAsync)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + subscribeAsync + ) + ); + } + + /// + /// Creates an observable sequence from a specified asynchronous Subscribe method. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the produced sequence. + /// Asynchronous method used to implement the resulting sequence's Subscribe method. + /// The observable sequence with the specified implementation for the Subscribe method. + /// + /// is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + public static IQbservable Create(this IQbservableProvider provider, Expression, Task>> subscribeAsync) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (subscribeAsync == null) + throw new ArgumentNullException(nameof(subscribeAsync)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + subscribeAsync + ) + ); + } + + /// + /// Creates an observable sequence from a specified cancellable asynchronous Subscribe method. + /// The CancellationToken passed to the asynchronous Subscribe method is tied to the returned disposable subscription, allowing best-effort cancellation. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the produced sequence. + /// Asynchronous method used to implement the resulting sequence's Subscribe method, returning an Action delegate that will be wrapped in an IDisposable. + /// The observable sequence with the specified implementation for the Subscribe method. + /// + /// is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + /// When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous subscribe function will be signaled. + public static IQbservable Create(this IQbservableProvider provider, Expression, CancellationToken, Task>> subscribeAsync) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (subscribeAsync == null) + throw new ArgumentNullException(nameof(subscribeAsync)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + subscribeAsync + ) + ); + } + + /// + /// Creates an observable sequence from a specified asynchronous Subscribe method. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the produced sequence. + /// Asynchronous method used to implement the resulting sequence's Subscribe method, returning an Action delegate that will be wrapped in an IDisposable. + /// The observable sequence with the specified implementation for the Subscribe method. + /// + /// is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + public static IQbservable Create(this IQbservableProvider provider, Expression, Task>> subscribeAsync) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (subscribeAsync == null) + throw new ArgumentNullException(nameof(subscribeAsync)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + subscribeAsync + ) + ); + } + + /// + /// Returns the elements of the specified sequence or the type parameter's default value in a singleton sequence if the sequence is empty. + /// + /// The type of the elements in the source sequence (if any), whose default value will be taken if the sequence is empty. + /// The sequence to return a default value for if it is empty. + /// An observable sequence that contains the default value for the TSource type if the source is empty; otherwise, the elements of the source itself. + /// + /// is null. + public static IQbservable DefaultIfEmpty(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Returns the elements of the specified sequence or the specified value in a singleton sequence if the sequence is empty. + /// + /// The type of the elements in the source sequence (if any), and the specified default value which will be taken if the sequence is empty. + /// The sequence to return the specified value for if it is empty. + /// The value to return if the sequence is empty. + /// An observable sequence that contains the specified default value if the source is empty; otherwise, the elements of the source itself. + /// + /// is null. + public static IQbservable DefaultIfEmpty(this IQbservable source, TSource defaultValue) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(defaultValue, typeof(TSource)) + ) + ); + } + + /// + /// Returns an observable sequence that invokes the specified factory function whenever a new observer subscribes. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the sequence returned by the factory function, and in the resulting sequence. + /// Observable factory function to invoke for each observer that subscribes to the resulting sequence. + /// An observable sequence whose observers trigger an invocation of the given observable factory function. + /// + /// is null. + public static IQbservable Defer(this IQbservableProvider provider, Expression>> observableFactory) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (observableFactory == null) + throw new ArgumentNullException(nameof(observableFactory)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + observableFactory + ) + ); + } + + /// + /// Returns an observable sequence that starts the specified asynchronous factory function whenever a new observer subscribes. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the sequence returned by the factory function, and in the resulting sequence. + /// Asynchronous factory function to start for each observer that subscribes to the resulting sequence. + /// An observable sequence whose observers trigger the given asynchronous observable factory function to be started. + /// + /// is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + public static IQbservable Defer(this IQbservableProvider provider, Expression>>> observableFactoryAsync) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (observableFactoryAsync == null) + throw new ArgumentNullException(nameof(observableFactoryAsync)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + observableFactoryAsync + ) + ); + } + + /// + /// Returns an observable sequence that starts the specified asynchronous factory function whenever a new observer subscribes. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the sequence returned by the factory function, and in the resulting sequence. + /// Asynchronous factory function to start for each observer that subscribes to the resulting sequence. + /// + /// If true, exceptions that occur after cancellation has been initiated by unsubscribing from the observable + /// this method returns will be handled and silently ignored. If false, they will go unobserved, meaning they + /// will eventually emerge through . + /// + /// An observable sequence whose observers trigger the given asynchronous observable factory function to be started. + /// + /// is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + public static IQbservable Defer(this IQbservableProvider provider, Expression>>> observableFactoryAsync, bool ignoreExceptionsAfterUnsubscribe) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (observableFactoryAsync == null) + throw new ArgumentNullException(nameof(observableFactoryAsync)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + observableFactoryAsync, + Expression.Constant(ignoreExceptionsAfterUnsubscribe, typeof(bool)) + ) + ); + } + + /// + /// Returns an observable sequence that starts the specified cancellable asynchronous factory function whenever a new observer subscribes. + /// The CancellationToken passed to the asynchronous factory function is tied to the returned disposable subscription, allowing best-effort cancellation. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the sequence returned by the factory function, and in the resulting sequence. + /// Asynchronous factory function to start for each observer that subscribes to the resulting sequence. + /// An observable sequence whose observers trigger the given asynchronous observable factory function to be started. + /// + /// is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + /// When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous observable factory function will be signaled. + public static IQbservable DeferAsync(this IQbservableProvider provider, Expression>>> observableFactoryAsync) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (observableFactoryAsync == null) + throw new ArgumentNullException(nameof(observableFactoryAsync)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + observableFactoryAsync + ) + ); + } + + /// + /// Returns an observable sequence that starts the specified cancellable asynchronous factory function whenever a new observer subscribes. + /// The CancellationToken passed to the asynchronous factory function is tied to the returned disposable subscription, allowing best-effort cancellation. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the sequence returned by the factory function, and in the resulting sequence. + /// Asynchronous factory function to start for each observer that subscribes to the resulting sequence. + /// + /// If true, exceptions that occur after cancellation has been initiated by unsubscribing from the observable + /// this method returns will be handled and silently ignored. If false, they will go unobserved, meaning they + /// will eventually emerge through . + /// + /// An observable sequence whose observers trigger the given asynchronous observable factory function to be started. + /// + /// is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + /// When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous observable factory function will be signaled. + public static IQbservable DeferAsync(this IQbservableProvider provider, Expression>>> observableFactoryAsync, bool ignoreExceptionsAfterUnsubscribe) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (observableFactoryAsync == null) + throw new ArgumentNullException(nameof(observableFactoryAsync)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + observableFactoryAsync, + Expression.Constant(ignoreExceptionsAfterUnsubscribe, typeof(bool)) + ) + ); + } + + /// + /// Time shifts the observable sequence to start propagating notifications at the specified absolute time. + /// The relative time intervals between the values are preserved. + /// + /// The type of the elements in the source sequence. + /// Source sequence to delay values for. + /// Absolute time used to shift the observable sequence; the relative time shift gets computed upon subscription. If this value is less than or equal to DateTimeOffset.UtcNow, the scheduler will dispatch observer callbacks as soon as possible. + /// Time-shifted sequence. + /// + /// is null. + /// + /// + /// This operator is less efficient than DelaySubscription because it records all notifications and time-delays those. This allows for immediate propagation of errors. + /// + /// + /// Observer callbacks for the resulting sequence will be run on the default scheduler. This effect is similar to using ObserveOn. + /// + /// + /// Exceptions signaled by the source sequence through an OnError callback are forwarded immediately to the result sequence. Any OnNext notifications that were in the queue at the point of the OnError callback will be dropped. + /// In order to delay error propagation, consider using the Observable.Materialize and Observable.Dematerialize operators, or use DelaySubscription. + /// + /// + public static IQbservable Delay(this IQbservable source, DateTimeOffset dueTime) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(dueTime, typeof(DateTimeOffset)) + ) + ); + } + + /// + /// Time shifts the observable sequence to start propagating notifications at the specified absolute time, using the specified scheduler to run timers. + /// The relative time intervals between the values are preserved. + /// + /// The type of the elements in the source sequence. + /// Source sequence to delay values for. + /// Absolute time used to shift the observable sequence; the relative time shift gets computed upon subscription. If this value is less than or equal to DateTimeOffset.UtcNow, the scheduler will dispatch observer callbacks as soon as possible. + /// Scheduler to run the delay timers on. + /// Time-shifted sequence. + /// + /// or is null. + /// + /// + /// This operator is less efficient than DelaySubscription because it records all notifications and time-delays those. This allows for immediate propagation of errors. + /// + /// + /// Observer callbacks for the resulting sequence will be run on the specified scheduler. This effect is similar to using ObserveOn. + /// + /// + /// Exceptions signaled by the source sequence through an OnError callback are forwarded immediately to the result sequence. Any OnNext notifications that were in the queue at the point of the OnError callback will be dropped. + /// In order to delay error propagation, consider using the Observable.Materialize and Observable.Dematerialize operators, or use DelaySubscription. + /// + /// + public static IQbservable Delay(this IQbservable source, DateTimeOffset dueTime, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(dueTime, typeof(DateTimeOffset)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Time shifts the observable sequence by the specified relative time duration. + /// The relative time intervals between the values are preserved. + /// + /// The type of the elements in the source sequence. + /// Source sequence to delay values for. + /// Relative time by which to shift the observable sequence. If this value is equal to TimeSpan.Zero, the scheduler will dispatch observer callbacks as soon as possible. + /// Time-shifted sequence. + /// + /// is null. + /// + /// is less than TimeSpan.Zero. + /// + /// + /// This operator is less efficient than DelaySubscription because it records all notifications and time-delays those. This allows for immediate propagation of errors. + /// + /// + /// Observer callbacks for the resulting sequence will be run on the default scheduler. This effect is similar to using ObserveOn. + /// + /// + /// Exceptions signaled by the source sequence through an OnError callback are forwarded immediately to the result sequence. Any OnNext notifications that were in the queue at the point of the OnError callback will be dropped. + /// In order to delay error propagation, consider using the Observable.Materialize and Observable.Dematerialize operators, or use DelaySubscription. + /// + /// + public static IQbservable Delay(this IQbservable source, TimeSpan dueTime) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(dueTime, typeof(TimeSpan)) + ) + ); + } + + /// + /// Time shifts the observable sequence by the specified relative time duration, using the specified scheduler to run timers. + /// The relative time intervals between the values are preserved. + /// + /// The type of the elements in the source sequence. + /// Source sequence to delay values for. + /// Relative time by which to shift the observable sequence. If this value is equal to TimeSpan.Zero, the scheduler will dispatch observer callbacks as soon as possible. + /// Scheduler to run the delay timers on. + /// Time-shifted sequence. + /// + /// or is null. + /// + /// is less than TimeSpan.Zero. + /// + /// + /// This operator is less efficient than DelaySubscription because it records all notifications and time-delays those. This allows for immediate propagation of errors. + /// + /// + /// Observer callbacks for the resulting sequence will be run on the specified scheduler. This effect is similar to using ObserveOn. + /// + /// + /// Exceptions signaled by the source sequence through an OnError callback are forwarded immediately to the result sequence. Any OnNext notifications that were in the queue at the point of the OnError callback will be dropped. + /// + /// + /// Exceptions signaled by the source sequence through an OnError callback are forwarded immediately to the result sequence. Any OnNext notifications that were in the queue at the point of the OnError callback will be dropped. + /// In order to delay error propagation, consider using the Observable.Materialize and Observable.Dematerialize operators, or use DelaySubscription. + /// + /// + public static IQbservable Delay(this IQbservable source, TimeSpan dueTime, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(dueTime, typeof(TimeSpan)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Time shifts the observable sequence based on a delay selector function for each element. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the delay sequences used to denote the delay duration of each element in the source sequence. + /// Source sequence to delay values for. + /// Selector function to retrieve a sequence indicating the delay for each given element. + /// Time-shifted sequence. + /// + /// or is null. + public static IQbservable Delay(this IQbservable source, Expression>> delayDurationSelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (delayDurationSelector == null) + throw new ArgumentNullException(nameof(delayDurationSelector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TDelay)), + source.Expression, + delayDurationSelector + ) + ); + } + + /// + /// Time shifts the observable sequence based on a subscription delay and a delay selector function for each element. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the delay sequences used to denote the delay duration of each element in the source sequence. + /// Source sequence to delay values for. + /// Sequence indicating the delay for the subscription to the source. + /// Selector function to retrieve a sequence indicating the delay for each given element. + /// Time-shifted sequence. + /// + /// or or is null. + public static IQbservable Delay(this IQbservable source, IObservable subscriptionDelay, Expression>> delayDurationSelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (subscriptionDelay == null) + throw new ArgumentNullException(nameof(subscriptionDelay)); + if (delayDurationSelector == null) + throw new ArgumentNullException(nameof(delayDurationSelector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TDelay)), + source.Expression, + GetSourceExpression(subscriptionDelay), + delayDurationSelector + ) + ); + } + + /// + /// Time shifts the observable sequence by delaying the subscription to the specified absolute time. + /// + /// The type of the elements in the source sequence. + /// Source sequence to delay subscription for. + /// Absolute time to perform the subscription at. + /// Time-shifted sequence. + /// + /// is null. + /// + /// + /// This operator is more efficient than Delay but postpones all side-effects of subscription and affects error propagation timing. + /// + /// + /// The side-effects of subscribing to the source sequence will be run on the default scheduler. Observer callbacks will not be affected. + /// + /// + public static IQbservable DelaySubscription(this IQbservable source, DateTimeOffset dueTime) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(dueTime, typeof(DateTimeOffset)) + ) + ); + } + + /// + /// Time shifts the observable sequence by delaying the subscription to the specified absolute time, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence. + /// Source sequence to delay subscription for. + /// Absolute time to perform the subscription at. + /// Scheduler to run the subscription delay timer on. + /// Time-shifted sequence. + /// + /// or is null. + /// + /// + /// This operator is more efficient than Delay but postpones all side-effects of subscription and affects error propagation timing. + /// + /// + /// The side-effects of subscribing to the source sequence will be run on the specified scheduler. Observer callbacks will not be affected. + /// + /// + public static IQbservable DelaySubscription(this IQbservable source, DateTimeOffset dueTime, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(dueTime, typeof(DateTimeOffset)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Time shifts the observable sequence by delaying the subscription with the specified relative time duration. + /// + /// The type of the elements in the source sequence. + /// Source sequence to delay subscription for. + /// Relative time shift of the subscription. + /// Time-shifted sequence. + /// + /// is null. + /// + /// is less than TimeSpan.Zero. + /// + /// + /// This operator is more efficient than Delay but postpones all side-effects of subscription and affects error propagation timing. + /// + /// + /// The side-effects of subscribing to the source sequence will be run on the default scheduler. Observer callbacks will not be affected. + /// + /// + public static IQbservable DelaySubscription(this IQbservable source, TimeSpan dueTime) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(dueTime, typeof(TimeSpan)) + ) + ); + } + + /// + /// Time shifts the observable sequence by delaying the subscription with the specified relative time duration, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence. + /// Source sequence to delay subscription for. + /// Relative time shift of the subscription. + /// Scheduler to run the subscription delay timer on. + /// Time-shifted sequence. + /// + /// or is null. + /// + /// is less than TimeSpan.Zero. + /// + /// + /// This operator is more efficient than Delay but postpones all side-effects of subscription and affects error propagation timing. + /// + /// + /// The side-effects of subscribing to the source sequence will be run on the specified scheduler. Observer callbacks will not be affected. + /// + /// + public static IQbservable DelaySubscription(this IQbservable source, TimeSpan dueTime, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(dueTime, typeof(TimeSpan)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Dematerializes the explicit notification values of an observable sequence as implicit notifications. + /// + /// The type of the elements materialized in the source sequence notification objects. + /// An observable sequence containing explicit notification values which have to be turned into implicit notifications. + /// An observable sequence exhibiting the behavior corresponding to the source sequence's notification values. + /// + /// is null. + public static IQbservable Dematerialize(this IQbservable> source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Returns an observable sequence that contains only distinct elements. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to retain distinct elements for. + /// An observable sequence only containing the distinct elements from the source sequence. + /// + /// is null. + /// Usage of this operator should be considered carefully due to the maintenance of an internal lookup structure which can grow large. + public static IQbservable Distinct(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Returns an observable sequence that contains only distinct elements according to the comparer. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to retain distinct elements for. + /// Equality comparer for source elements. + /// An observable sequence only containing the distinct elements from the source sequence. + /// + /// or is null. + /// Usage of this operator should be considered carefully due to the maintenance of an internal lookup structure which can grow large. + public static IQbservable Distinct(this IQbservable source, IEqualityComparer comparer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(comparer, typeof(IEqualityComparer)) + ) + ); + } + + /// + /// Returns an observable sequence that contains only distinct elements according to the keySelector. + /// + /// The type of the elements in the source sequence. + /// The type of the discriminator key computed for each element in the source sequence. + /// An observable sequence to retain distinct elements for. + /// A function to compute the comparison key for each element. + /// An observable sequence only containing the distinct elements, based on a computed key value, from the source sequence. + /// + /// or is null. + /// Usage of this operator should be considered carefully due to the maintenance of an internal lookup structure which can grow large. + public static IQbservable Distinct(this IQbservable source, Expression> keySelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey)), + source.Expression, + keySelector + ) + ); + } + + /// + /// Returns an observable sequence that contains only distinct elements according to the keySelector and the comparer. + /// + /// The type of the elements in the source sequence. + /// The type of the discriminator key computed for each element in the source sequence. + /// An observable sequence to retain distinct elements for. + /// A function to compute the comparison key for each element. + /// Equality comparer for source elements. + /// An observable sequence only containing the distinct elements, based on a computed key value, from the source sequence. + /// + /// or or is null. + /// Usage of this operator should be considered carefully due to the maintenance of an internal lookup structure which can grow large. + public static IQbservable Distinct(this IQbservable source, Expression> keySelector, IEqualityComparer comparer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey)), + source.Expression, + keySelector, + Expression.Constant(comparer, typeof(IEqualityComparer)) + ) + ); + } + + /// + /// Returns an observable sequence that contains only distinct contiguous elements. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to retain distinct contiguous elements for. + /// An observable sequence only containing the distinct contiguous elements from the source sequence. + /// + /// is null. + public static IQbservable DistinctUntilChanged(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Returns an observable sequence that contains only distinct contiguous elements according to the comparer. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to retain distinct contiguous elements for. + /// Equality comparer for source elements. + /// An observable sequence only containing the distinct contiguous elements from the source sequence. + /// + /// or is null. + public static IQbservable DistinctUntilChanged(this IQbservable source, IEqualityComparer comparer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(comparer, typeof(IEqualityComparer)) + ) + ); + } + + /// + /// Returns an observable sequence that contains only distinct contiguous elements according to the keySelector. + /// + /// The type of the elements in the source sequence. + /// The type of the discriminator key computed for each element in the source sequence. + /// An observable sequence to retain distinct contiguous elements for, based on a computed key value. + /// A function to compute the comparison key for each element. + /// An observable sequence only containing the distinct contiguous elements, based on a computed key value, from the source sequence. + /// + /// or is null. + public static IQbservable DistinctUntilChanged(this IQbservable source, Expression> keySelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey)), + source.Expression, + keySelector + ) + ); + } + + /// + /// Returns an observable sequence that contains only distinct contiguous elements according to the keySelector and the comparer. + /// + /// The type of the elements in the source sequence. + /// The type of the discriminator key computed for each element in the source sequence. + /// An observable sequence to retain distinct contiguous elements for, based on a computed key value. + /// A function to compute the comparison key for each element. + /// Equality comparer for computed key values. + /// An observable sequence only containing the distinct contiguous elements, based on a computed key value, from the source sequence. + /// + /// or or is null. + public static IQbservable DistinctUntilChanged(this IQbservable source, Expression> keySelector, IEqualityComparer comparer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey)), + source.Expression, + keySelector, + Expression.Constant(comparer, typeof(IEqualityComparer)) + ) + ); + } + + /// + /// Invokes the observer's methods for each message in the source sequence. + /// This method can be used for debugging, logging, etc. of query behavior by intercepting the message stream to run arbitrary actions for messages on the pipeline. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Observer whose methods to invoke as part of the source sequence's observation. + /// The source sequence with the side-effecting behavior applied. + /// + /// or is null. + public static IQbservable Do(this IQbservable source, IObserver observer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (observer == null) + throw new ArgumentNullException(nameof(observer)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(observer, typeof(IObserver)) + ) + ); + } + + /// + /// Invokes an action for each element in the observable sequence, and propagates all observer messages through the result sequence. + /// This method can be used for debugging, logging, etc. of query behavior by intercepting the message stream to run arbitrary actions for messages on the pipeline. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Action to invoke for each element in the observable sequence. + /// The source sequence with the side-effecting behavior applied. + /// + /// or is null. + public static IQbservable Do(this IQbservable source, Expression> onNext) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (onNext == null) + throw new ArgumentNullException(nameof(onNext)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + onNext + ) + ); + } + + /// + /// Invokes an action for each element in the observable sequence and invokes an action upon graceful termination of the observable sequence. + /// This method can be used for debugging, logging, etc. of query behavior by intercepting the message stream to run arbitrary actions for messages on the pipeline. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Action to invoke for each element in the observable sequence. + /// Action to invoke upon graceful termination of the observable sequence. + /// The source sequence with the side-effecting behavior applied. + /// + /// or or is null. + public static IQbservable Do(this IQbservable source, Expression> onNext, Expression onCompleted) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (onNext == null) + throw new ArgumentNullException(nameof(onNext)); + if (onCompleted == null) + throw new ArgumentNullException(nameof(onCompleted)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + onNext, + onCompleted + ) + ); + } + + /// + /// Invokes an action for each element in the observable sequence and invokes an action upon exceptional termination of the observable sequence. + /// This method can be used for debugging, logging, etc. of query behavior by intercepting the message stream to run arbitrary actions for messages on the pipeline. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Action to invoke for each element in the observable sequence. + /// Action to invoke upon exceptional termination of the observable sequence. + /// The source sequence with the side-effecting behavior applied. + /// + /// or or is null. + public static IQbservable Do(this IQbservable source, Expression> onNext, Expression> onError) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (onNext == null) + throw new ArgumentNullException(nameof(onNext)); + if (onError == null) + throw new ArgumentNullException(nameof(onError)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + onNext, + onError + ) + ); + } + + /// + /// Invokes an action for each element in the observable sequence and invokes an action upon graceful or exceptional termination of the observable sequence. + /// This method can be used for debugging, logging, etc. of query behavior by intercepting the message stream to run arbitrary actions for messages on the pipeline. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Action to invoke for each element in the observable sequence. + /// Action to invoke upon exceptional termination of the observable sequence. + /// Action to invoke upon graceful termination of the observable sequence. + /// The source sequence with the side-effecting behavior applied. + /// + /// or or or is null. + public static IQbservable Do(this IQbservable source, Expression> onNext, Expression> onError, Expression onCompleted) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (onNext == null) + throw new ArgumentNullException(nameof(onNext)); + if (onError == null) + throw new ArgumentNullException(nameof(onError)); + if (onCompleted == null) + throw new ArgumentNullException(nameof(onCompleted)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + onNext, + onError, + onCompleted + ) + ); + } + + /// + /// Repeats the given as long as the specified holds, where the is evaluated after each repeated completed. + /// + /// The type of the elements in the source sequence. + /// Source to repeat as long as the function evaluates to true. + /// Condition that will be evaluated upon the completion of an iteration through the , to determine whether repetition of the source is required. + /// The observable sequence obtained by concatenating the sequence as long as the holds. + /// + /// or is null. + public static IQbservable DoWhile(this IQbservable source, Expression> condition) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (condition == null) + throw new ArgumentNullException(nameof(condition)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + condition + ) + ); + } + + /// + /// Returns the element at a specified index in a sequence. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to return the element from. + /// The zero-based index of the element to retrieve. + /// An observable sequence that produces the element at the specified position in the source sequence. + /// + /// is null. + /// + /// is less than zero. + /// (Asynchronous) is greater than or equal to the number of elements in the source sequence. + public static IQbservable ElementAt(this IQbservable source, int index) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(index, typeof(int)) + ) + ); + } + + /// + /// Returns the element at a specified index in a sequence or a default value if the index is out of range. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to return the element from. + /// The zero-based index of the element to retrieve. + /// An observable sequence that produces the element at the specified position in the source sequence, or a default value if the index is outside the bounds of the source sequence. + /// + /// is null. + /// + /// is less than zero. + public static IQbservable ElementAtOrDefault(this IQbservable source, int index) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(index, typeof(int)) + ) + ); + } + + /// + /// Returns an empty observable sequence. + /// + /// Query provider used to construct the data source. + /// The type used for the type parameter of the resulting sequence. + /// An observable sequence with no elements. + public static IQbservable Empty(this IQbservableProvider provider) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)) + ) + ); + } + + /// + /// Returns an empty observable sequence, using the specified scheduler to send out the single OnCompleted message. + /// + /// Query provider used to construct the data source. + /// The type used for the type parameter of the resulting sequence. + /// Scheduler to send the termination call on. + /// An observable sequence with no elements. + /// + /// is null. + public static IQbservable Empty(this IQbservableProvider provider, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Returns an empty observable sequence, using the specified scheduler to send out the single OnCompleted message. + /// + /// Query provider used to construct the data source. + /// The type used for the type parameter of the resulting sequence. + /// Scheduler to send the termination call on. + /// Object solely used to infer the type of the type parameter. This parameter is typically used when creating a sequence of anonymously typed elements. + /// An observable sequence with no elements. + /// + /// is null. + public static IQbservable Empty(this IQbservableProvider provider, IScheduler scheduler, TResult witness) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(scheduler, typeof(IScheduler)), + Expression.Constant(witness, typeof(TResult)) + ) + ); + } + + /// + /// Returns an empty observable sequence. + /// + /// Query provider used to construct the data source. + /// The type used for the type parameter of the resulting sequence. + /// Object solely used to infer the type of the type parameter. This parameter is typically used when creating a sequence of anonymously typed elements. + /// An observable sequence with no elements. + public static IQbservable Empty(this IQbservableProvider provider, TResult witness) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(witness, typeof(TResult)) + ) + ); + } + + /// + /// Invokes a specified action after the source observable sequence terminates gracefully or exceptionally. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Action to invoke after the source observable sequence terminates. + /// Source sequence with the action-invoking termination behavior applied. + /// + /// or is null. + public static IQbservable Finally(this IQbservable source, Expression finallyAction) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (finallyAction == null) + throw new ArgumentNullException(nameof(finallyAction)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + finallyAction + ) + ); + } + + /// + /// Returns the first element of an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// Sequence containing the first element in the observable sequence. + /// + /// is null. + /// (Asynchronous) The source sequence is empty. + public static IQbservable FirstAsync(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Returns the first element of an observable sequence that satisfies the condition in the predicate. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// A predicate function to evaluate for elements in the source sequence. + /// Sequence containing the first element in the observable sequence that satisfies the condition in the predicate. + /// + /// or is null. + /// (Asynchronous) No element satisfies the condition in the predicate. -or- The source sequence is empty. + public static IQbservable FirstAsync(this IQbservable source, Expression> predicate) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (predicate == null) + throw new ArgumentNullException(nameof(predicate)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + predicate + ) + ); + } + + /// + /// Returns the first element of an observable sequence, or a default value if no such element exists. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// Sequence containing the first element in the observable sequence, or a default value if no such element exists. + /// + /// is null. + public static IQbservable FirstOrDefaultAsync(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Returns the first element of an observable sequence that satisfies the condition in the predicate, or a default value if no such element exists. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// A predicate function to evaluate for elements in the source sequence. + /// Sequence containing the first element in the observable sequence that satisfies the condition in the predicate, or a default value if no such element exists. + /// + /// or is null. + public static IQbservable FirstOrDefaultAsync(this IQbservable source, Expression> predicate) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (predicate == null) + throw new ArgumentNullException(nameof(predicate)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + predicate + ) + ); + } + + /// + /// Concatenates the observable sequences obtained by running the for each element in the given enumerable . + /// + /// Query provider used to construct the data source. + /// The type of the elements in the enumerable source sequence. + /// The type of the elements in the observable result sequence. + /// Enumerable source for which each element will be mapped onto an observable source that will be concatenated in the result sequence. + /// Function to select an observable source for each element in the . + /// The observable sequence obtained by concatenating the sources returned by for each element in the . + /// + /// or is null. + public static IQbservable For(this IQbservableProvider provider, IEnumerable source, Expression>> resultSelector) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + GetSourceExpression(source), + resultSelector + ) + ); + } + + /// + /// Converts an asynchronous action into an observable sequence. Each subscription to the resulting sequence causes the action to be started. + /// + /// Query provider used to construct the data source. + /// Asynchronous action to convert. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// + /// is null. + public static IQbservable FromAsync(this IQbservableProvider provider, Expression> actionAsync) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (actionAsync == null) + throw new ArgumentNullException(nameof(actionAsync)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + actionAsync + ) + ); + } + + /// + /// Converts an asynchronous action into an observable sequence. Each subscription to the resulting sequence causes the action to be started. + /// + /// Query provider used to construct the data source. + /// Asynchronous action to convert. + /// Controls how the tasks's progress is observed. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// + /// is null. + public static IQbservable FromAsync(this IQbservableProvider provider, Expression> actionAsync, TaskObservationOptions options) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (actionAsync == null) + throw new ArgumentNullException(nameof(actionAsync)); + if (options == null) + throw new ArgumentNullException(nameof(options)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + actionAsync, + Expression.Constant(options, typeof(TaskObservationOptions)) + ) + ); + } + + /// + /// Converts an asynchronous action into an observable sequence. Each subscription to the resulting sequence causes the action to be started. + /// + /// Query provider used to construct the data source. + /// Asynchronous action to convert. + /// Scheduler on which to notify observers. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// + /// is null or is null. + public static IQbservable FromAsync(this IQbservableProvider provider, Expression> actionAsync, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (actionAsync == null) + throw new ArgumentNullException(nameof(actionAsync)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + actionAsync, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Converts an asynchronous action into an observable sequence. Each subscription to the resulting sequence causes the action to be started. + /// The CancellationToken passed to the asynchronous action is tied to the observable sequence's subscription that triggered the action's invocation and can be used for best-effort cancellation. + /// + /// Query provider used to construct the data source. + /// Asynchronous action to convert. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous function will be signaled. + /// + /// is null. + public static IQbservable FromAsync(this IQbservableProvider provider, Expression> actionAsync) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (actionAsync == null) + throw new ArgumentNullException(nameof(actionAsync)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + actionAsync + ) + ); + } + + /// + /// Converts an asynchronous action into an observable sequence. Each subscription to the resulting sequence causes the action to be started. + /// The CancellationToken passed to the asynchronous action is tied to the observable sequence's subscription that triggered the action's invocation and can be used for best-effort cancellation. + /// + /// Query provider used to construct the data source. + /// Asynchronous action to convert. + /// Controls how the tasks's progress is observed. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous function will be signaled. + /// + /// is null. + public static IQbservable FromAsync(this IQbservableProvider provider, Expression> actionAsync, TaskObservationOptions options) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (actionAsync == null) + throw new ArgumentNullException(nameof(actionAsync)); + if (options == null) + throw new ArgumentNullException(nameof(options)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + actionAsync, + Expression.Constant(options, typeof(TaskObservationOptions)) + ) + ); + } + + /// + /// Converts an asynchronous action into an observable sequence. Each subscription to the resulting sequence causes the action to be started. + /// The CancellationToken passed to the asynchronous action is tied to the observable sequence's subscription that triggered the action's invocation and can be used for best-effort cancellation. + /// + /// Query provider used to construct the data source. + /// Asynchronous action to convert. + /// Scheduler on which to notify observers. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous function will be signaled. + /// + /// is null or is null. + public static IQbservable FromAsync(this IQbservableProvider provider, Expression> actionAsync, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (actionAsync == null) + throw new ArgumentNullException(nameof(actionAsync)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + actionAsync, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Converts an asynchronous function into an observable sequence. Each subscription to the resulting sequence causes the function to be started. + /// + /// Query provider used to construct the data source. + /// The type of the result returned by the asynchronous function. + /// Asynchronous function to convert. + /// An observable sequence exposing the result of invoking the function, or an exception. + /// + /// is null. + public static IQbservable FromAsync(this IQbservableProvider provider, Expression>> functionAsync) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (functionAsync == null) + throw new ArgumentNullException(nameof(functionAsync)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + functionAsync + ) + ); + } + + /// + /// Converts an asynchronous function into an observable sequence. Each subscription to the resulting sequence causes the function to be started. + /// The CancellationToken passed to the asynchronous function is tied to the observable sequence's subscription that triggered the function's invocation and can be used for best-effort cancellation. + /// + /// Query provider used to construct the data source. + /// The type of the result returned by the asynchronous function. + /// Asynchronous function to convert. + /// An observable sequence exposing the result of invoking the function, or an exception. + /// + /// is null. + /// When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous function will be signaled. + public static IQbservable FromAsync(this IQbservableProvider provider, Expression>> functionAsync) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (functionAsync == null) + throw new ArgumentNullException(nameof(functionAsync)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + functionAsync + ) + ); + } + + /// + /// Converts an asynchronous function into an observable sequence. Each subscription to the resulting sequence causes the function to be started. + /// + /// Query provider used to construct the data source. + /// The type of the result returned by the asynchronous function. + /// Asynchronous function to convert. + /// Controls how the tasks's progress is observed. + /// An observable sequence exposing the result of invoking the function, or an exception. + /// + /// is null. + public static IQbservable FromAsync(this IQbservableProvider provider, Expression>> functionAsync, TaskObservationOptions options) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (functionAsync == null) + throw new ArgumentNullException(nameof(functionAsync)); + if (options == null) + throw new ArgumentNullException(nameof(options)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + functionAsync, + Expression.Constant(options, typeof(TaskObservationOptions)) + ) + ); + } + + /// + /// Converts an asynchronous function into an observable sequence. Each subscription to the resulting sequence causes the function to be started. + /// The CancellationToken passed to the asynchronous function is tied to the observable sequence's subscription that triggered the function's invocation and can be used for best-effort cancellation. + /// + /// Query provider used to construct the data source. + /// The type of the result returned by the asynchronous function. + /// Asynchronous function to convert. + /// Controls how the tasks's progress is observed. + /// An observable sequence exposing the result of invoking the function, or an exception. + /// + /// is null. + /// When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous function will be signaled. + public static IQbservable FromAsync(this IQbservableProvider provider, Expression>> functionAsync, TaskObservationOptions options) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (functionAsync == null) + throw new ArgumentNullException(nameof(functionAsync)); + if (options == null) + throw new ArgumentNullException(nameof(options)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + functionAsync, + Expression.Constant(options, typeof(TaskObservationOptions)) + ) + ); + } + + /// + /// Converts an asynchronous function into an observable sequence. Each subscription to the resulting sequence causes the function to be started. + /// + /// Query provider used to construct the data source. + /// The type of the result returned by the asynchronous function. + /// Asynchronous function to convert. + /// Scheduler on which to notify observers. + /// An observable sequence exposing the result of invoking the function, or an exception. + /// + /// is null or is null. + public static IQbservable FromAsync(this IQbservableProvider provider, Expression>> functionAsync, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (functionAsync == null) + throw new ArgumentNullException(nameof(functionAsync)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + functionAsync, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Converts an asynchronous function into an observable sequence. Each subscription to the resulting sequence causes the function to be started. + /// The CancellationToken passed to the asynchronous function is tied to the observable sequence's subscription that triggered the function's invocation and can be used for best-effort cancellation. + /// + /// Query provider used to construct the data source. + /// The type of the result returned by the asynchronous function. + /// Asynchronous function to convert. + /// Scheduler on which to notify observers. + /// An observable sequence exposing the result of invoking the function, or an exception. + /// + /// is null or is null. + /// When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous function will be signaled. + public static IQbservable FromAsync(this IQbservableProvider provider, Expression>> functionAsync, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (functionAsync == null) + throw new ArgumentNullException(nameof(functionAsync)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + functionAsync, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Converts an Action-based .NET event to an observable sequence. Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events conforming to the standard .NET event pattern, use any of the FromEventPattern overloads instead. + /// + /// Query provider used to construct the data source. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The observable sequence that contains the event argument objects passed to the invocations of the underlying .NET event. + /// + /// or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEvent, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEvent, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEvent calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// + public static IQbservable FromEvent(this IQbservableProvider provider, Expression> addHandler, Expression> removeHandler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (addHandler == null) + throw new ArgumentNullException(nameof(addHandler)); + if (removeHandler == null) + throw new ArgumentNullException(nameof(removeHandler)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + addHandler, + removeHandler + ) + ); + } + + /// + /// Converts an Action-based .NET event to an observable sequence. Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events conforming to the standard .NET event pattern, use any of the FromEventPattern overloads instead. + /// + /// Query provider used to construct the data source. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains the event argument objects passed to the invocations of the underlying .NET event. + /// + /// or or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEvent calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEvent that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// + public static IQbservable FromEvent(this IQbservableProvider provider, Expression> addHandler, Expression> removeHandler, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (addHandler == null) + throw new ArgumentNullException(nameof(addHandler)); + if (removeHandler == null) + throw new ArgumentNullException(nameof(removeHandler)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + addHandler, + removeHandler, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Converts a .NET event to an observable sequence, using a supplied event delegate type. Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events conforming to the standard .NET event pattern, use any of the FromEventPattern overloads instead. + /// + /// Query provider used to construct the data source. + /// The delegate type of the event to be converted. + /// The type of the event data generated by the event. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The observable sequence that contains the event argument objects passed to the invocations of the underlying .NET event. + /// + /// or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEvent, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEvent, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEvent calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// + public static IQbservable FromEvent(this IQbservableProvider provider, Expression> addHandler, Expression> removeHandler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (addHandler == null) + throw new ArgumentNullException(nameof(addHandler)); + if (removeHandler == null) + throw new ArgumentNullException(nameof(removeHandler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TDelegate), typeof(TEventArgs)), + Expression.Constant(provider, typeof(IQbservableProvider)), + addHandler, + removeHandler + ) + ); + } + + /// + /// Converts a .NET event to an observable sequence, using a supplied event delegate type. Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events conforming to the standard .NET event pattern, use any of the FromEventPattern overloads instead. + /// + /// Query provider used to construct the data source. + /// The delegate type of the event to be converted. + /// The type of the event data generated by the event. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains the event argument objects passed to the invocations of the underlying .NET event. + /// + /// or or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEvent calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEvent that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// + public static IQbservable FromEvent(this IQbservableProvider provider, Expression> addHandler, Expression> removeHandler, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (addHandler == null) + throw new ArgumentNullException(nameof(addHandler)); + if (removeHandler == null) + throw new ArgumentNullException(nameof(removeHandler)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TDelegate), typeof(TEventArgs)), + Expression.Constant(provider, typeof(IQbservableProvider)), + addHandler, + removeHandler, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Converts a .NET event to an observable sequence, using a conversion function to obtain the event delegate. Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events conforming to the standard .NET event pattern, use any of the FromEventPattern overloads instead. + /// + /// Query provider used to construct the data source. + /// The delegate type of the event to be converted. + /// The type of the event data generated by the event. + /// A function used to convert the given event handler to a delegate compatible with the underlying .NET event. The resulting delegate is used in calls to the addHandler and removeHandler action parameters. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The observable sequence that contains the event argument objects passed to the invocations of the underlying .NET event. + /// + /// or or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEvent, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEvent, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEvent calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// + public static IQbservable FromEvent(this IQbservableProvider provider, Expression, TDelegate>> conversion, Expression> addHandler, Expression> removeHandler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (conversion == null) + throw new ArgumentNullException(nameof(conversion)); + if (addHandler == null) + throw new ArgumentNullException(nameof(addHandler)); + if (removeHandler == null) + throw new ArgumentNullException(nameof(removeHandler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TDelegate), typeof(TEventArgs)), + Expression.Constant(provider, typeof(IQbservableProvider)), + conversion, + addHandler, + removeHandler + ) + ); + } + + /// + /// Converts a .NET event to an observable sequence, using a conversion function to obtain the event delegate. Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events conforming to the standard .NET event pattern, use any of the FromEventPattern overloads instead. + /// + /// Query provider used to construct the data source. + /// The delegate type of the event to be converted. + /// The type of the event data generated by the event. + /// A function used to convert the given event handler to a delegate compatible with the underlying .NET event. The resulting delegate is used in calls to the addHandler and removeHandler action parameters. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains the event argument objects passed to the invocations of the underlying .NET event. + /// + /// or or or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEvent calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEvent that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// + public static IQbservable FromEvent(this IQbservableProvider provider, Expression, TDelegate>> conversion, Expression> addHandler, Expression> removeHandler, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (conversion == null) + throw new ArgumentNullException(nameof(conversion)); + if (addHandler == null) + throw new ArgumentNullException(nameof(addHandler)); + if (removeHandler == null) + throw new ArgumentNullException(nameof(removeHandler)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TDelegate), typeof(TEventArgs)), + Expression.Constant(provider, typeof(IQbservableProvider)), + conversion, + addHandler, + removeHandler, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Converts a generic Action-based .NET event to an observable sequence. Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events conforming to the standard .NET event pattern, use any of the FromEventPattern overloads instead. + /// + /// Query provider used to construct the data source. + /// The type of the event data generated by the event. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The observable sequence that contains the event argument objects passed to the invocations of the underlying .NET event. + /// + /// or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEvent, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEvent, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEvent calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// + public static IQbservable FromEvent(this IQbservableProvider provider, Expression>> addHandler, Expression>> removeHandler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (addHandler == null) + throw new ArgumentNullException(nameof(addHandler)); + if (removeHandler == null) + throw new ArgumentNullException(nameof(removeHandler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TEventArgs)), + Expression.Constant(provider, typeof(IQbservableProvider)), + addHandler, + removeHandler + ) + ); + } + + /// + /// Converts a generic Action-based .NET event to an observable sequence. Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events conforming to the standard .NET event pattern, use any of the FromEventPattern overloads instead. + /// + /// Query provider used to construct the data source. + /// The type of the event data generated by the event. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains the event argument objects passed to the invocations of the underlying .NET event. + /// + /// or or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEvent calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEvent that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// + public static IQbservable FromEvent(this IQbservableProvider provider, Expression>> addHandler, Expression>> removeHandler, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (addHandler == null) + throw new ArgumentNullException(nameof(addHandler)); + if (removeHandler == null) + throw new ArgumentNullException(nameof(removeHandler)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TEventArgs)), + Expression.Constant(provider, typeof(IQbservableProvider)), + addHandler, + removeHandler, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Converts a .NET event, conforming to the standard .NET event pattern based on , to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Query provider used to construct the data source. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// + /// or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEventPattern, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEventPattern, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// + public static IQbservable> FromEventPattern(this IQbservableProvider provider, Expression> addHandler, Expression> removeHandler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (addHandler == null) + throw new ArgumentNullException(nameof(addHandler)); + if (removeHandler == null) + throw new ArgumentNullException(nameof(removeHandler)); + + return provider.CreateQuery>( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + addHandler, + removeHandler + ) + ); + } + + /// + /// Converts a .NET event, conforming to the standard .NET event pattern based on , to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Query provider used to construct the data source. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// + /// or or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEventPattern that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// + public static IQbservable> FromEventPattern(this IQbservableProvider provider, Expression> addHandler, Expression> removeHandler, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (addHandler == null) + throw new ArgumentNullException(nameof(addHandler)); + if (removeHandler == null) + throw new ArgumentNullException(nameof(removeHandler)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery>( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + addHandler, + removeHandler, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Converts an instance .NET event, conforming to the standard .NET event pattern with an parameter, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// Reflection is used to discover the event based on the target object type and the specified event name. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Query provider used to construct the data source. + /// Object instance that exposes the event to convert. + /// Name of the event to convert. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// + /// or is null. + /// The event could not be found. -or- The event does not conform to the standard .NET event pattern. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEventPattern, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEventPattern, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// + public static IQbservable> FromEventPattern(this IQbservableProvider provider, object target, string eventName) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (target == null) + throw new ArgumentNullException(nameof(target)); + if (eventName == null) + throw new ArgumentNullException(nameof(eventName)); + + return provider.CreateQuery>( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(target, typeof(object)), + Expression.Constant(eventName, typeof(string)) + ) + ); + } + + /// + /// Converts an instance .NET event, conforming to the standard .NET event pattern with an parameter, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// Reflection is used to discover the event based on the target object type and the specified event name. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Query provider used to construct the data source. + /// Object instance that exposes the event to convert. + /// Name of the event to convert. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// + /// or or is null. + /// The event could not be found. -or- The event does not conform to the standard .NET event pattern. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEventPattern that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// + public static IQbservable> FromEventPattern(this IQbservableProvider provider, object target, string eventName, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (target == null) + throw new ArgumentNullException(nameof(target)); + if (eventName == null) + throw new ArgumentNullException(nameof(eventName)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery>( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(target, typeof(object)), + Expression.Constant(eventName, typeof(string)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Converts a static .NET event, conforming to the standard .NET event pattern with an parameter, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// Reflection is used to discover the event based on the specified type and the specified event name. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Query provider used to construct the data source. + /// Type that exposes the static event to convert. + /// Name of the event to convert. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// + /// or is null. + /// The event could not be found. -or- The event does not conform to the standard .NET event pattern. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEventPattern, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEventPattern, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// + public static IQbservable> FromEventPattern(this IQbservableProvider provider, Type type, string eventName) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (type == null) + throw new ArgumentNullException(nameof(type)); + if (eventName == null) + throw new ArgumentNullException(nameof(eventName)); + + return provider.CreateQuery>( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(type, typeof(Type)), + Expression.Constant(eventName, typeof(string)) + ) + ); + } + + /// + /// Converts a static .NET event, conforming to the standard .NET event pattern with an parameter, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// Reflection is used to discover the event based on the specified type and the specified event name. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Query provider used to construct the data source. + /// Type that exposes the static event to convert. + /// Name of the event to convert. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// + /// or or is null. + /// The event could not be found. -or- The event does not conform to the standard .NET event pattern. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEventPattern that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// + public static IQbservable> FromEventPattern(this IQbservableProvider provider, Type type, string eventName, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (type == null) + throw new ArgumentNullException(nameof(type)); + if (eventName == null) + throw new ArgumentNullException(nameof(eventName)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery>( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(type, typeof(Type)), + Expression.Constant(eventName, typeof(string)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Converts a .NET event, conforming to the standard .NET event pattern based on a supplied event delegate type, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Query provider used to construct the data source. + /// The delegate type of the event to be converted. + /// The type of the event data generated by the event. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// + /// or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEventPattern, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEventPattern, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// + public static IQbservable> FromEventPattern(this IQbservableProvider provider, Expression> addHandler, Expression> removeHandler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (addHandler == null) + throw new ArgumentNullException(nameof(addHandler)); + if (removeHandler == null) + throw new ArgumentNullException(nameof(removeHandler)); + + return provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TDelegate), typeof(TEventArgs)), + Expression.Constant(provider, typeof(IQbservableProvider)), + addHandler, + removeHandler + ) + ); + } + + /// + /// Converts a .NET event, conforming to the standard .NET event pattern based on a supplied event delegate type, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Query provider used to construct the data source. + /// The delegate type of the event to be converted. + /// The type of the event data generated by the event. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// + /// or or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEventPattern that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// + public static IQbservable> FromEventPattern(this IQbservableProvider provider, Expression> addHandler, Expression> removeHandler, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (addHandler == null) + throw new ArgumentNullException(nameof(addHandler)); + if (removeHandler == null) + throw new ArgumentNullException(nameof(removeHandler)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TDelegate), typeof(TEventArgs)), + Expression.Constant(provider, typeof(IQbservableProvider)), + addHandler, + removeHandler, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Converts a .NET event, conforming to the standard .NET event pattern based on , to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Query provider used to construct the data source. + /// The delegate type of the event to be converted. + /// The type of the event data generated by the event. + /// A function used to convert the given event handler to a delegate compatible with the underlying .NET event. The resulting delegate is used in calls to the addHandler and removeHandler action parameters. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// + /// or or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEventPattern, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEventPattern, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// + public static IQbservable> FromEventPattern(this IQbservableProvider provider, Expression, TDelegate>> conversion, Expression> addHandler, Expression> removeHandler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (conversion == null) + throw new ArgumentNullException(nameof(conversion)); + if (addHandler == null) + throw new ArgumentNullException(nameof(addHandler)); + if (removeHandler == null) + throw new ArgumentNullException(nameof(removeHandler)); + + return provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TDelegate), typeof(TEventArgs)), + Expression.Constant(provider, typeof(IQbservableProvider)), + conversion, + addHandler, + removeHandler + ) + ); + } + + /// + /// Converts a .NET event, conforming to the standard .NET event pattern based on , to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Query provider used to construct the data source. + /// The delegate type of the event to be converted. + /// The type of the event data generated by the event. + /// A function used to convert the given event handler to a delegate compatible with the underlying .NET event. The resulting delegate is used in calls to the addHandler and removeHandler action parameters. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// + /// or or or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEventPattern that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// + public static IQbservable> FromEventPattern(this IQbservableProvider provider, Expression, TDelegate>> conversion, Expression> addHandler, Expression> removeHandler, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (conversion == null) + throw new ArgumentNullException(nameof(conversion)); + if (addHandler == null) + throw new ArgumentNullException(nameof(addHandler)); + if (removeHandler == null) + throw new ArgumentNullException(nameof(removeHandler)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TDelegate), typeof(TEventArgs)), + Expression.Constant(provider, typeof(IQbservableProvider)), + conversion, + addHandler, + removeHandler, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Converts a .NET event, conforming to the standard .NET event pattern based on a supplied event delegate type with a strongly typed sender parameter, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Query provider used to construct the data source. + /// The delegate type of the event to be converted. + /// The type of the sender that raises the event. + /// The type of the event data generated by the event. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// + /// or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEventPattern, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEventPattern, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// + public static IQbservable> FromEventPattern(this IQbservableProvider provider, Expression> addHandler, Expression> removeHandler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (addHandler == null) + throw new ArgumentNullException(nameof(addHandler)); + if (removeHandler == null) + throw new ArgumentNullException(nameof(removeHandler)); + + return provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TDelegate), typeof(TSender), typeof(TEventArgs)), + Expression.Constant(provider, typeof(IQbservableProvider)), + addHandler, + removeHandler + ) + ); + } + + /// + /// Converts a .NET event, conforming to the standard .NET event pattern based on a supplied event delegate type with a strongly typed sender parameter, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Query provider used to construct the data source. + /// The delegate type of the event to be converted. + /// The type of the sender that raises the event. + /// The type of the event data generated by the event. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// + /// or or is null. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEventPattern that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// + public static IQbservable> FromEventPattern(this IQbservableProvider provider, Expression> addHandler, Expression> removeHandler, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (addHandler == null) + throw new ArgumentNullException(nameof(addHandler)); + if (removeHandler == null) + throw new ArgumentNullException(nameof(removeHandler)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TDelegate), typeof(TSender), typeof(TEventArgs)), + Expression.Constant(provider, typeof(IQbservableProvider)), + addHandler, + removeHandler, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Converts a .NET event, conforming to the standard .NET event pattern based on , to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Query provider used to construct the data source. + /// The type of the event data generated by the event. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEventPattern, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEventPattern, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// + public static IQbservable> FromEventPattern(this IQbservableProvider provider, Expression>> addHandler, Expression>> removeHandler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (addHandler == null) + throw new ArgumentNullException(nameof(addHandler)); + if (removeHandler == null) + throw new ArgumentNullException(nameof(removeHandler)); + + return provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TEventArgs)), + Expression.Constant(provider, typeof(IQbservableProvider)), + addHandler, + removeHandler + ) + ); + } + + /// + /// Converts a .NET event, conforming to the standard .NET event pattern based on , to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Query provider used to construct the data source. + /// The type of the event data generated by the event. + /// Action that attaches the given event handler to the underlying .NET event. + /// Action that detaches the given event handler from the underlying .NET event. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEventPattern that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// + public static IQbservable> FromEventPattern(this IQbservableProvider provider, Expression>> addHandler, Expression>> removeHandler, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (addHandler == null) + throw new ArgumentNullException(nameof(addHandler)); + if (removeHandler == null) + throw new ArgumentNullException(nameof(removeHandler)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TEventArgs)), + Expression.Constant(provider, typeof(IQbservableProvider)), + addHandler, + removeHandler, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Converts an instance .NET event, conforming to the standard .NET event pattern with strongly typed event arguments, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// Reflection is used to discover the event based on the target object type and the specified event name. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Query provider used to construct the data source. + /// The type of the event data generated by the event. + /// Object instance that exposes the event to convert. + /// Name of the event to convert. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// + /// or is null. + /// The event could not be found. -or- The event does not conform to the standard .NET event pattern. -or- The event's second argument type is not assignable to TEventArgs. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEventPattern, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEventPattern, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// + public static IQbservable> FromEventPattern(this IQbservableProvider provider, object target, string eventName) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (target == null) + throw new ArgumentNullException(nameof(target)); + if (eventName == null) + throw new ArgumentNullException(nameof(eventName)); + + return provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TEventArgs)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(target, typeof(object)), + Expression.Constant(eventName, typeof(string)) + ) + ); + } + + /// + /// Converts an instance .NET event, conforming to the standard .NET event pattern with strongly typed event arguments, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// Reflection is used to discover the event based on the target object type and the specified event name. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Query provider used to construct the data source. + /// The type of the event data generated by the event. + /// Object instance that exposes the event to convert. + /// Name of the event to convert. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// + /// or or is null. + /// The event could not be found. -or- The event does not conform to the standard .NET event pattern. -or- The event's second argument type is not assignable to TEventArgs. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEventPattern that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// + public static IQbservable> FromEventPattern(this IQbservableProvider provider, object target, string eventName, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (target == null) + throw new ArgumentNullException(nameof(target)); + if (eventName == null) + throw new ArgumentNullException(nameof(eventName)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TEventArgs)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(target, typeof(object)), + Expression.Constant(eventName, typeof(string)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Converts a static .NET event, conforming to the standard .NET event pattern with strongly typed event arguments, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// Reflection is used to discover the event based on the specified type and the specified event name. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Query provider used to construct the data source. + /// The type of the event data generated by the event. + /// Type that exposes the static event to convert. + /// Name of the event to convert. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// + /// or is null. + /// The event could not be found. -or- The event does not conform to the standard .NET event pattern. -or- The event's second argument type is not assignable to TEventArgs. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEventPattern, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEventPattern, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// + public static IQbservable> FromEventPattern(this IQbservableProvider provider, Type type, string eventName) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (type == null) + throw new ArgumentNullException(nameof(type)); + if (eventName == null) + throw new ArgumentNullException(nameof(eventName)); + + return provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TEventArgs)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(type, typeof(Type)), + Expression.Constant(eventName, typeof(string)) + ) + ); + } + + /// + /// Converts a static .NET event, conforming to the standard .NET event pattern with strongly typed event arguments, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// Reflection is used to discover the event based on the specified type and the specified event name. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Query provider used to construct the data source. + /// The type of the event data generated by the event. + /// Type that exposes the static event to convert. + /// Name of the event to convert. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// + /// or or is null. + /// The event could not be found. -or- The event does not conform to the standard .NET event pattern. -or- The event's second argument type is not assignable to TEventArgs. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEventPattern that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// + public static IQbservable> FromEventPattern(this IQbservableProvider provider, Type type, string eventName, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (type == null) + throw new ArgumentNullException(nameof(type)); + if (eventName == null) + throw new ArgumentNullException(nameof(eventName)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TEventArgs)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(type, typeof(Type)), + Expression.Constant(eventName, typeof(string)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Converts an instance .NET event, conforming to the standard .NET event pattern with a strongly typed sender and strongly typed event arguments, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// Reflection is used to discover the event based on the target object type and the specified event name. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Query provider used to construct the data source. + /// The type of the sender that raises the event. + /// The type of the event data generated by the event. + /// Object instance that exposes the event to convert. + /// Name of the event to convert. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// + /// or is null. + /// The event could not be found. -or- The event does not conform to the standard .NET event pattern. -or- The event's first argument type is not assignable to TSender. -or- The event's second argument type is not assignable to TEventArgs. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEventPattern, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEventPattern, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// + public static IQbservable> FromEventPattern(this IQbservableProvider provider, object target, string eventName) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (target == null) + throw new ArgumentNullException(nameof(target)); + if (eventName == null) + throw new ArgumentNullException(nameof(eventName)); + + return provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSender), typeof(TEventArgs)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(target, typeof(object)), + Expression.Constant(eventName, typeof(string)) + ) + ); + } + + /// + /// Converts an instance .NET event, conforming to the standard .NET event pattern with a strongly typed sender and strongly typed event arguments, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// Reflection is used to discover the event based on the target object type and the specified event name. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Query provider used to construct the data source. + /// The type of the sender that raises the event. + /// The type of the event data generated by the event. + /// Object instance that exposes the event to convert. + /// Name of the event to convert. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// + /// or or is null. + /// The event could not be found. -or- The event does not conform to the standard .NET event pattern. -or- The event's first argument type is not assignable to TSender. -or- The event's second argument type is not assignable to TEventArgs. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEventPattern that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// + public static IQbservable> FromEventPattern(this IQbservableProvider provider, object target, string eventName, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (target == null) + throw new ArgumentNullException(nameof(target)); + if (eventName == null) + throw new ArgumentNullException(nameof(eventName)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSender), typeof(TEventArgs)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(target, typeof(object)), + Expression.Constant(eventName, typeof(string)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Converts a static .NET event, conforming to the standard .NET event pattern with a strongly typed sender and strongly typed event arguments, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// Reflection is used to discover the event based on the specified type and the specified event name. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Query provider used to construct the data source. + /// The type of the sender that raises the event. + /// The type of the event data generated by the event. + /// Type that exposes the static event to convert. + /// Name of the event to convert. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// + /// or is null. + /// The event could not be found. -or- The event does not conform to the standard .NET event pattern. -or- The event's first argument type is not assignable to TSender. -or- The event's second argument type is not assignable to TEventArgs. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// The current is captured during the call to FromEventPattern, and is used to post add and remove handler invocations. + /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks. + /// + /// + /// If no SynchronizationContext is present at the point of calling FromEventPattern, add and remove handler invocations are made synchronously on the thread + /// making the Subscribe or Dispose call, respectively. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so + /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions + /// more concise and easier to understand. + /// + /// + /// + public static IQbservable> FromEventPattern(this IQbservableProvider provider, Type type, string eventName) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (type == null) + throw new ArgumentNullException(nameof(type)); + if (eventName == null) + throw new ArgumentNullException(nameof(eventName)); + + return provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSender), typeof(TEventArgs)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(type, typeof(Type)), + Expression.Constant(eventName, typeof(string)) + ) + ); + } + + /// + /// Converts a static .NET event, conforming to the standard .NET event pattern with a strongly typed sender and strongly typed event arguments, to an observable sequence. + /// Each event invocation is surfaced through an OnNext message in the resulting sequence. + /// Reflection is used to discover the event based on the specified type and the specified event name. + /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead. + /// + /// Query provider used to construct the data source. + /// The type of the sender that raises the event. + /// The type of the event data generated by the event. + /// Type that exposes the static event to convert. + /// Name of the event to convert. + /// The scheduler to run the add and remove event handler logic on. + /// The observable sequence that contains data representations of invocations of the underlying .NET event. + /// + /// or or is null. + /// The event could not be found. -or- The event does not conform to the standard .NET event pattern. -or- The event's first argument type is not assignable to TSender. -or- The event's second argument type is not assignable to TEventArgs. + /// + /// + /// Add and remove handler invocations are made whenever the number of observers grows beyond zero. + /// As such, an event handler may be shared by multiple simultaneously active observers, using a subject for multicasting. + /// + /// + /// Add and remove handler invocations are run on the specified scheduler. This behavior allows add and remove handler operations for thread-affine events to be + /// accessed from the same context, as required by some UI frameworks. + /// + /// + /// It's recommended to lift FromEventPattern calls outside event stream query expressions. This best practice reduces clutter of bridging code inside queries, + /// making the query expressions more concise and easier to understand. This has additional benefits for overloads of FromEventPattern that omit the IScheduler + /// parameter. For more information, see the remarks section on those overloads. + /// + /// + /// + public static IQbservable> FromEventPattern(this IQbservableProvider provider, Type type, string eventName, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (type == null) + throw new ArgumentNullException(nameof(type)); + if (eventName == null) + throw new ArgumentNullException(nameof(eventName)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSender), typeof(TEventArgs)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(type, typeof(Type)), + Expression.Constant(eventName, typeof(string)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Generates an observable sequence by running a state-driven loop producing the sequence's elements. + /// + /// Query provider used to construct the data source. + /// The type of the state used in the generator loop. + /// The type of the elements in the produced sequence. + /// Initial state. + /// Condition to terminate generation (upon returning false). + /// Iteration step function. + /// Selector function for results produced in the sequence. + /// The generated sequence. + /// + /// or or is null. + public static IQbservable Generate(this IQbservableProvider provider, TState initialState, Expression> condition, Expression> iterate, Expression> resultSelector) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (condition == null) + throw new ArgumentNullException(nameof(condition)); + if (iterate == null) + throw new ArgumentNullException(nameof(iterate)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TState), typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(initialState, typeof(TState)), + condition, + iterate, + resultSelector + ) + ); + } + + /// + /// Generates an observable sequence by running a state-driven loop producing the sequence's elements, using the specified scheduler to send out observer messages. + /// + /// Query provider used to construct the data source. + /// The type of the state used in the generator loop. + /// The type of the elements in the produced sequence. + /// Initial state. + /// Condition to terminate generation (upon returning false). + /// Iteration step function. + /// Selector function for results produced in the sequence. + /// Scheduler on which to run the generator loop. + /// The generated sequence. + /// + /// or or or is null. + public static IQbservable Generate(this IQbservableProvider provider, TState initialState, Expression> condition, Expression> iterate, Expression> resultSelector, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (condition == null) + throw new ArgumentNullException(nameof(condition)); + if (iterate == null) + throw new ArgumentNullException(nameof(iterate)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TState), typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(initialState, typeof(TState)), + condition, + iterate, + resultSelector, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Generates an observable sequence by running a state-driven and temporal loop producing the sequence's elements. + /// + /// Query provider used to construct the data source. + /// The type of the state used in the generator loop. + /// The type of the elements in the produced sequence. + /// Initial state. + /// Condition to terminate generation (upon returning false). + /// Iteration step function. + /// Selector function for results produced in the sequence. + /// Time selector function to control the speed of values being produced each iteration. + /// The generated sequence. + /// + /// or or or is null. + public static IQbservable Generate(this IQbservableProvider provider, TState initialState, Expression> condition, Expression> iterate, Expression> resultSelector, Expression> timeSelector) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (condition == null) + throw new ArgumentNullException(nameof(condition)); + if (iterate == null) + throw new ArgumentNullException(nameof(iterate)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + if (timeSelector == null) + throw new ArgumentNullException(nameof(timeSelector)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TState), typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(initialState, typeof(TState)), + condition, + iterate, + resultSelector, + timeSelector + ) + ); + } + + /// + /// Generates an observable sequence by running a state-driven and temporal loop producing the sequence's elements. + /// + /// Query provider used to construct the data source. + /// The type of the state used in the generator loop. + /// The type of the elements in the produced sequence. + /// Initial state. + /// Condition to terminate generation (upon returning false). + /// Iteration step function. + /// Selector function for results produced in the sequence. + /// Time selector function to control the speed of values being produced each iteration. + /// The generated sequence. + /// + /// or or or is null. + public static IQbservable Generate(this IQbservableProvider provider, TState initialState, Expression> condition, Expression> iterate, Expression> resultSelector, Expression> timeSelector) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (condition == null) + throw new ArgumentNullException(nameof(condition)); + if (iterate == null) + throw new ArgumentNullException(nameof(iterate)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + if (timeSelector == null) + throw new ArgumentNullException(nameof(timeSelector)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TState), typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(initialState, typeof(TState)), + condition, + iterate, + resultSelector, + timeSelector + ) + ); + } + + /// + /// Generates an observable sequence by running a state-driven and temporal loop producing the sequence's elements, using the specified scheduler to run timers and to send out observer messages. + /// + /// Query provider used to construct the data source. + /// The type of the state used in the generator loop. + /// The type of the elements in the produced sequence. + /// Initial state. + /// Condition to terminate generation (upon returning false). + /// Iteration step function. + /// Selector function for results produced in the sequence. + /// Time selector function to control the speed of values being produced each iteration. + /// Scheduler on which to run the generator loop. + /// The generated sequence. + /// + /// or or or or is null. + public static IQbservable Generate(this IQbservableProvider provider, TState initialState, Expression> condition, Expression> iterate, Expression> resultSelector, Expression> timeSelector, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (condition == null) + throw new ArgumentNullException(nameof(condition)); + if (iterate == null) + throw new ArgumentNullException(nameof(iterate)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + if (timeSelector == null) + throw new ArgumentNullException(nameof(timeSelector)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TState), typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(initialState, typeof(TState)), + condition, + iterate, + resultSelector, + timeSelector, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Generates an observable sequence by running a state-driven and temporal loop producing the sequence's elements, using the specified scheduler to run timers and to send out observer messages. + /// + /// Query provider used to construct the data source. + /// The type of the state used in the generator loop. + /// The type of the elements in the produced sequence. + /// Initial state. + /// Condition to terminate generation (upon returning false). + /// Iteration step function. + /// Selector function for results produced in the sequence. + /// Time selector function to control the speed of values being produced each iteration. + /// Scheduler on which to run the generator loop. + /// The generated sequence. + /// + /// or or or or is null. + public static IQbservable Generate(this IQbservableProvider provider, TState initialState, Expression> condition, Expression> iterate, Expression> resultSelector, Expression> timeSelector, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (condition == null) + throw new ArgumentNullException(nameof(condition)); + if (iterate == null) + throw new ArgumentNullException(nameof(iterate)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + if (timeSelector == null) + throw new ArgumentNullException(nameof(timeSelector)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TState), typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(initialState, typeof(TState)), + condition, + iterate, + resultSelector, + timeSelector, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Groups the elements of an observable sequence according to a specified key selector function. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// + /// or is null. + public static IQbservable> GroupBy(this IQbservable source, Expression> keySelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey)), + source.Expression, + keySelector + ) + ); + } + + /// + /// Groups the elements of an observable sequence with the specified initial capacity according to a specified key selector function. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// The initial number of elements that the underlying dictionary can contain. + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// + /// or is null. + /// + /// is less than 0. + public static IQbservable> GroupBy(this IQbservable source, Expression> keySelector, int capacity) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey)), + source.Expression, + keySelector, + Expression.Constant(capacity, typeof(int)) + ) + ); + } + + /// + /// Groups the elements of an observable sequence with the specified initial capacity according to a specified key selector function and comparer. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// The initial number of elements that the underlying dictionary can contain. + /// An equality comparer to compare keys with. + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// + /// or or is null. + /// + /// is less than 0. + public static IQbservable> GroupBy(this IQbservable source, Expression> keySelector, int capacity, IEqualityComparer comparer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey)), + source.Expression, + keySelector, + Expression.Constant(capacity, typeof(int)), + Expression.Constant(comparer, typeof(IEqualityComparer)) + ) + ); + } + + /// + /// Groups the elements of an observable sequence according to a specified key selector function and comparer. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// An equality comparer to compare keys with. + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// + /// or or is null. + public static IQbservable> GroupBy(this IQbservable source, Expression> keySelector, IEqualityComparer comparer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey)), + source.Expression, + keySelector, + Expression.Constant(comparer, typeof(IEqualityComparer)) + ) + ); + } + + /// + /// Groups the elements of an observable sequence and selects the resulting elements by using a specified function. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// The type of the elements within the groups computed for each element in the source sequence. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A function to map each source element to an element in an observable group. + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// + /// or or is null. + public static IQbservable> GroupBy(this IQbservable source, Expression> keySelector, Expression> elementSelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (elementSelector == null) + throw new ArgumentNullException(nameof(elementSelector)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey), typeof(TElement)), + source.Expression, + keySelector, + elementSelector + ) + ); + } + + /// + /// Groups the elements of an observable sequence with the specified initial capacity and selects the resulting elements by using a specified function. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// The type of the elements within the groups computed for each element in the source sequence. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A function to map each source element to an element in an observable group. + /// The initial number of elements that the underlying dictionary can contain. + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// + /// or or is null. + /// + /// is less than 0. + public static IQbservable> GroupBy(this IQbservable source, Expression> keySelector, Expression> elementSelector, int capacity) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (elementSelector == null) + throw new ArgumentNullException(nameof(elementSelector)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey), typeof(TElement)), + source.Expression, + keySelector, + elementSelector, + Expression.Constant(capacity, typeof(int)) + ) + ); + } + + /// + /// Groups the elements of an observable sequence with the specified initial capacity according to a specified key selector function and comparer and selects the resulting elements by using a specified function. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// The type of the elements within the groups computed for each element in the source sequence. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A function to map each source element to an element in an observable group. + /// The initial number of elements that the underlying dictionary can contain. + /// An equality comparer to compare keys with. + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// + /// or or or is null. + /// + /// is less than 0. + public static IQbservable> GroupBy(this IQbservable source, Expression> keySelector, Expression> elementSelector, int capacity, IEqualityComparer comparer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (elementSelector == null) + throw new ArgumentNullException(nameof(elementSelector)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey), typeof(TElement)), + source.Expression, + keySelector, + elementSelector, + Expression.Constant(capacity, typeof(int)), + Expression.Constant(comparer, typeof(IEqualityComparer)) + ) + ); + } + + /// + /// Groups the elements of an observable sequence according to a specified key selector function and comparer and selects the resulting elements by using a specified function. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// The type of the elements within the groups computed for each element in the source sequence. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A function to map each source element to an element in an observable group. + /// An equality comparer to compare keys with. + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// + /// or or or is null. + public static IQbservable> GroupBy(this IQbservable source, Expression> keySelector, Expression> elementSelector, IEqualityComparer comparer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (elementSelector == null) + throw new ArgumentNullException(nameof(elementSelector)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey), typeof(TElement)), + source.Expression, + keySelector, + elementSelector, + Expression.Constant(comparer, typeof(IEqualityComparer)) + ) + ); + } + + /// + /// Groups the elements of an observable sequence according to a specified key selector function. + /// A duration selector function is used to control the lifetime of groups. When a group expires, it receives an OnCompleted notification. When a new element with the same + /// key value as a reclaimed group occurs, the group will be reborn with a new lifetime request. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// The type of the elements in the duration sequences obtained for each group to denote its lifetime. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A function to signal the expiration of a group. + /// + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// If a group's lifetime expires, a new group with the same key value can be created once an element with such a key value is encountered. + /// + /// + /// or or is null. + public static IQbservable> GroupByUntil(this IQbservable source, Expression> keySelector, Expression, IObservable>> durationSelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (durationSelector == null) + throw new ArgumentNullException(nameof(durationSelector)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey), typeof(TDuration)), + source.Expression, + keySelector, + durationSelector + ) + ); + } + + /// + /// Groups the elements of an observable sequence with the specified initial capacity according to a specified key selector function. + /// A duration selector function is used to control the lifetime of groups. When a group expires, it receives an OnCompleted notification. When a new element with the same + /// key value as a reclaimed group occurs, the group will be reborn with a new lifetime request. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// The type of the elements in the duration sequences obtained for each group to denote its lifetime. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A function to signal the expiration of a group. + /// The initial number of elements that the underlying dictionary can contain. + /// + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// If a group's lifetime expires, a new group with the same key value can be created once an element with such a key value is encountered. + /// + /// + /// or or is null. + /// + /// is less than 0. + public static IQbservable> GroupByUntil(this IQbservable source, Expression> keySelector, Expression, IObservable>> durationSelector, int capacity) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (durationSelector == null) + throw new ArgumentNullException(nameof(durationSelector)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey), typeof(TDuration)), + source.Expression, + keySelector, + durationSelector, + Expression.Constant(capacity, typeof(int)) + ) + ); + } + + /// + /// Groups the elements of an observable sequence with the specified initial capacity according to a specified key selector function and comparer. + /// A duration selector function is used to control the lifetime of groups. When a group expires, it receives an OnCompleted notification. When a new element with the same + /// key value as a reclaimed group occurs, the group will be reborn with a new lifetime request. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// The type of the elements in the duration sequences obtained for each group to denote its lifetime. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A function to signal the expiration of a group. + /// The initial number of elements that the underlying dictionary can contain. + /// An equality comparer to compare keys with. + /// + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// If a group's lifetime expires, a new group with the same key value can be created once an element with such a key value is encountered. + /// + /// + /// or or or is null. + /// + /// is less than 0. + public static IQbservable> GroupByUntil(this IQbservable source, Expression> keySelector, Expression, IObservable>> durationSelector, int capacity, IEqualityComparer comparer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (durationSelector == null) + throw new ArgumentNullException(nameof(durationSelector)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey), typeof(TDuration)), + source.Expression, + keySelector, + durationSelector, + Expression.Constant(capacity, typeof(int)), + Expression.Constant(comparer, typeof(IEqualityComparer)) + ) + ); + } + + /// + /// Groups the elements of an observable sequence according to a specified key selector function and comparer. + /// A duration selector function is used to control the lifetime of groups. When a group expires, it receives an OnCompleted notification. When a new element with the same + /// key value as a reclaimed group occurs, the group will be reborn with a new lifetime request. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// The type of the elements in the duration sequences obtained for each group to denote its lifetime. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A function to signal the expiration of a group. + /// An equality comparer to compare keys with. + /// + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// If a group's lifetime expires, a new group with the same key value can be created once an element with such a key value is encountered. + /// + /// + /// or or or is null. + public static IQbservable> GroupByUntil(this IQbservable source, Expression> keySelector, Expression, IObservable>> durationSelector, IEqualityComparer comparer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (durationSelector == null) + throw new ArgumentNullException(nameof(durationSelector)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey), typeof(TDuration)), + source.Expression, + keySelector, + durationSelector, + Expression.Constant(comparer, typeof(IEqualityComparer)) + ) + ); + } + + /// + /// Groups the elements of an observable sequence according to a specified key selector function and selects the resulting elements by using a specified function. + /// A duration selector function is used to control the lifetime of groups. When a group expires, it receives an OnCompleted notification. When a new element with the same + /// key value as a reclaimed group occurs, the group will be reborn with a new lifetime request. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// The type of the elements within the groups computed for each element in the source sequence. + /// The type of the elements in the duration sequences obtained for each group to denote its lifetime. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A function to map each source element to an element in an observable group. + /// A function to signal the expiration of a group. + /// + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// If a group's lifetime expires, a new group with the same key value can be created once an element with such a key value is encountered. + /// + /// + /// or or or is null. + public static IQbservable> GroupByUntil(this IQbservable source, Expression> keySelector, Expression> elementSelector, Expression, IObservable>> durationSelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (elementSelector == null) + throw new ArgumentNullException(nameof(elementSelector)); + if (durationSelector == null) + throw new ArgumentNullException(nameof(durationSelector)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey), typeof(TElement), typeof(TDuration)), + source.Expression, + keySelector, + elementSelector, + durationSelector + ) + ); + } + + /// + /// Groups the elements of an observable sequence with the specified initial capacity according to a specified key selector function and selects the resulting elements by using a specified function. + /// A duration selector function is used to control the lifetime of groups. When a group expires, it receives an OnCompleted notification. When a new element with the same + /// key value as a reclaimed group occurs, the group will be reborn with a new lifetime request. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// The type of the elements within the groups computed for each element in the source sequence. + /// The type of the elements in the duration sequences obtained for each group to denote its lifetime. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A function to map each source element to an element in an observable group. + /// A function to signal the expiration of a group. + /// The initial number of elements that the underlying dictionary can contain. + /// + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// If a group's lifetime expires, a new group with the same key value can be created once an element with such a key value is encountered. + /// + /// + /// or or or is null. + /// + /// is less than 0. + public static IQbservable> GroupByUntil(this IQbservable source, Expression> keySelector, Expression> elementSelector, Expression, IObservable>> durationSelector, int capacity) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (elementSelector == null) + throw new ArgumentNullException(nameof(elementSelector)); + if (durationSelector == null) + throw new ArgumentNullException(nameof(durationSelector)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey), typeof(TElement), typeof(TDuration)), + source.Expression, + keySelector, + elementSelector, + durationSelector, + Expression.Constant(capacity, typeof(int)) + ) + ); + } + + /// + /// Groups the elements of an observable sequence with the specified initial capacity according to a specified key selector function and comparer and selects the resulting elements by using a specified function. + /// A duration selector function is used to control the lifetime of groups. When a group expires, it receives an OnCompleted notification. When a new element with the same + /// key value as a reclaimed group occurs, the group will be reborn with a new lifetime request. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// The type of the elements within the groups computed for each element in the source sequence. + /// The type of the elements in the duration sequences obtained for each group to denote its lifetime. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A function to map each source element to an element in an observable group. + /// A function to signal the expiration of a group. + /// The initial number of elements that the underlying dictionary can contain. + /// An equality comparer to compare keys with. + /// + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// If a group's lifetime expires, a new group with the same key value can be created once an element with such a key value is encountered. + /// + /// + /// or or or or is null. + /// + /// is less than 0. + public static IQbservable> GroupByUntil(this IQbservable source, Expression> keySelector, Expression> elementSelector, Expression, IObservable>> durationSelector, int capacity, IEqualityComparer comparer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (elementSelector == null) + throw new ArgumentNullException(nameof(elementSelector)); + if (durationSelector == null) + throw new ArgumentNullException(nameof(durationSelector)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey), typeof(TElement), typeof(TDuration)), + source.Expression, + keySelector, + elementSelector, + durationSelector, + Expression.Constant(capacity, typeof(int)), + Expression.Constant(comparer, typeof(IEqualityComparer)) + ) + ); + } + + /// + /// Groups the elements of an observable sequence according to a specified key selector function and comparer and selects the resulting elements by using a specified function. + /// A duration selector function is used to control the lifetime of groups. When a group expires, it receives an OnCompleted notification. When a new element with the same + /// key value as a reclaimed group occurs, the group will be reborn with a new lifetime request. + /// + /// The type of the elements in the source sequence. + /// The type of the grouping key computed for each element in the source sequence. + /// The type of the elements within the groups computed for each element in the source sequence. + /// The type of the elements in the duration sequences obtained for each group to denote its lifetime. + /// An observable sequence whose elements to group. + /// A function to extract the key for each element. + /// A function to map each source element to an element in an observable group. + /// A function to signal the expiration of a group. + /// An equality comparer to compare keys with. + /// + /// A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. + /// If a group's lifetime expires, a new group with the same key value can be created once an element with such a key value is encountered. + /// + /// + /// or or or or is null. + public static IQbservable> GroupByUntil(this IQbservable source, Expression> keySelector, Expression> elementSelector, Expression, IObservable>> durationSelector, IEqualityComparer comparer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (elementSelector == null) + throw new ArgumentNullException(nameof(elementSelector)); + if (durationSelector == null) + throw new ArgumentNullException(nameof(durationSelector)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey), typeof(TElement), typeof(TDuration)), + source.Expression, + keySelector, + elementSelector, + durationSelector, + Expression.Constant(comparer, typeof(IEqualityComparer)) + ) + ); + } + + /// + /// Correlates the elements of two sequences based on overlapping durations, and groups the results. + /// + /// The type of the elements in the left source sequence. + /// The type of the elements in the right source sequence. + /// The type of the elements in the duration sequence denoting the computed duration of each element in the left source sequence. + /// The type of the elements in the duration sequence denoting the computed duration of each element in the right source sequence. + /// The type of the elements in the result sequence, obtained by invoking the result selector function for source elements with overlapping duration. + /// The left observable sequence to join elements for. + /// The right observable sequence to join elements for. + /// A function to select the duration of each element of the left observable sequence, used to determine overlap. + /// A function to select the duration of each element of the right observable sequence, used to determine overlap. + /// A function invoked to compute a result element for any element of the left sequence with overlapping elements from the right observable sequence. + /// An observable sequence that contains result elements computed from source elements that have an overlapping duration. + /// + /// or or or or is null. + public static IQbservable GroupJoin(this IQbservable left, IObservable right, Expression>> leftDurationSelector, Expression>> rightDurationSelector, Expression, TResult>> resultSelector) + { + if (left == null) + throw new ArgumentNullException(nameof(left)); + if (right == null) + throw new ArgumentNullException(nameof(right)); + if (leftDurationSelector == null) + throw new ArgumentNullException(nameof(leftDurationSelector)); + if (rightDurationSelector == null) + throw new ArgumentNullException(nameof(rightDurationSelector)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return left.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TLeft), typeof(TRight), typeof(TLeftDuration), typeof(TRightDuration), typeof(TResult)), + left.Expression, + GetSourceExpression(right), + leftDurationSelector, + rightDurationSelector, + resultSelector + ) + ); + } + + /// + /// If the specified evaluates true, select the sequence. Otherwise, return an empty sequence. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the result sequence. + /// Condition evaluated to decide which sequence to return. + /// Sequence returned in case evaluates true. + /// + /// if evaluates true; an empty sequence otherwise. + /// + /// or is null. + public static IQbservable If(this IQbservableProvider provider, Expression> condition, IObservable thenSource) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (condition == null) + throw new ArgumentNullException(nameof(condition)); + if (thenSource == null) + throw new ArgumentNullException(nameof(thenSource)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + condition, + GetSourceExpression(thenSource) + ) + ); + } + + /// + /// If the specified evaluates true, select the sequence. Otherwise, select the sequence. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the result sequence. + /// Condition evaluated to decide which sequence to return. + /// Sequence returned in case evaluates true. + /// Sequence returned in case evaluates false. + /// + /// if evaluates true; otherwise. + /// + /// or or is null. + public static IQbservable If(this IQbservableProvider provider, Expression> condition, IObservable thenSource, IObservable elseSource) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (condition == null) + throw new ArgumentNullException(nameof(condition)); + if (thenSource == null) + throw new ArgumentNullException(nameof(thenSource)); + if (elseSource == null) + throw new ArgumentNullException(nameof(elseSource)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + condition, + GetSourceExpression(thenSource), + GetSourceExpression(elseSource) + ) + ); + } + + /// + /// If the specified evaluates true, select the sequence. Otherwise, return an empty sequence generated on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the result sequence. + /// Condition evaluated to decide which sequence to return. + /// Sequence returned in case evaluates true. + /// Scheduler to generate an empty sequence on in case evaluates false. + /// + /// if evaluates true; an empty sequence otherwise. + /// + /// or or is null. + public static IQbservable If(this IQbservableProvider provider, Expression> condition, IObservable thenSource, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (condition == null) + throw new ArgumentNullException(nameof(condition)); + if (thenSource == null) + throw new ArgumentNullException(nameof(thenSource)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + condition, + GetSourceExpression(thenSource), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Ignores all elements in an observable sequence leaving only the termination messages. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// An empty observable sequence that signals termination, successful or exceptional, of the source sequence. + /// + /// is null. + public static IQbservable IgnoreElements(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Returns an observable sequence that produces a value after each period. + /// + /// Query provider used to construct the data source. + /// Period for producing the values in the resulting sequence. If this value is equal to TimeSpan.Zero, the timer will recur as fast as possible. + /// An observable sequence that produces a value after each period. + /// + /// is less than TimeSpan.Zero. + /// + /// Intervals are measured between the start of subsequent notifications, not between the end of the previous and the start of the next notification. + /// If the observer takes longer than the interval period to handle the message, the subsequent notification will be delivered immediately after the + /// current one has been handled. In case you need to control the time between the end and the start of consecutive notifications, consider using the + /// + /// operator instead. + /// + public static IQbservable Interval(this IQbservableProvider provider, TimeSpan period) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(period, typeof(TimeSpan)) + ) + ); + } + + /// + /// Returns an observable sequence that produces a value after each period, using the specified scheduler to run timers and to send out observer messages. + /// + /// Query provider used to construct the data source. + /// Period for producing the values in the resulting sequence. If this value is equal to TimeSpan.Zero, the timer will recur as fast as possible. + /// Scheduler to run the timer on. + /// An observable sequence that produces a value after each period. + /// + /// is less than TimeSpan.Zero. + /// + /// is null. + /// + /// Intervals are measured between the start of subsequent notifications, not between the end of the previous and the start of the next notification. + /// If the observer takes longer than the interval period to handle the message, the subsequent notification will be delivered immediately after the + /// current one has been handled. In case you need to control the time between the end and the start of consecutive notifications, consider using the + /// + /// operator instead. + /// + public static IQbservable Interval(this IQbservableProvider provider, TimeSpan period, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(period, typeof(TimeSpan)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Determines whether an observable sequence is empty. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to check for emptiness. + /// An observable sequence containing a single element determining whether the source sequence is empty. + /// + /// is null. + public static IQbservable IsEmpty(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Correlates the elements of two sequences based on overlapping durations. + /// + /// The type of the elements in the left source sequence. + /// The type of the elements in the right source sequence. + /// The type of the elements in the duration sequence denoting the computed duration of each element in the left source sequence. + /// The type of the elements in the duration sequence denoting the computed duration of each element in the right source sequence. + /// The type of the elements in the result sequence, obtained by invoking the result selector function for source elements with overlapping duration. + /// The left observable sequence to join elements for. + /// The right observable sequence to join elements for. + /// A function to select the duration of each element of the left observable sequence, used to determine overlap. + /// A function to select the duration of each element of the right observable sequence, used to determine overlap. + /// A function invoked to compute a result element for any two overlapping elements of the left and right observable sequences. + /// An observable sequence that contains result elements computed from source elements that have an overlapping duration. + /// + /// or or or or is null. + public static IQbservable Join(this IQbservable left, IObservable right, Expression>> leftDurationSelector, Expression>> rightDurationSelector, Expression> resultSelector) + { + if (left == null) + throw new ArgumentNullException(nameof(left)); + if (right == null) + throw new ArgumentNullException(nameof(right)); + if (leftDurationSelector == null) + throw new ArgumentNullException(nameof(leftDurationSelector)); + if (rightDurationSelector == null) + throw new ArgumentNullException(nameof(rightDurationSelector)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return left.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TLeft), typeof(TRight), typeof(TLeftDuration), typeof(TRightDuration), typeof(TResult)), + left.Expression, + GetSourceExpression(right), + leftDurationSelector, + rightDurationSelector, + resultSelector + ) + ); + } + + /// + /// Returns the last element of an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// Sequence containing the last element in the observable sequence. + /// + /// is null. + /// (Asynchronous) The source sequence is empty. + public static IQbservable LastAsync(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Returns the last element of an observable sequence that satisfies the condition in the predicate. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// A predicate function to evaluate for elements in the source sequence. + /// Sequence containing the last element in the observable sequence that satisfies the condition in the predicate. + /// + /// or is null. + /// (Asynchronous) No element satisfies the condition in the predicate. -or- The source sequence is empty. + public static IQbservable LastAsync(this IQbservable source, Expression> predicate) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (predicate == null) + throw new ArgumentNullException(nameof(predicate)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + predicate + ) + ); + } + + /// + /// Returns the last element of an observable sequence, or a default value if no such element exists. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// Sequence containing the last element in the observable sequence, or a default value if no such element exists. + /// + /// is null. + public static IQbservable LastOrDefaultAsync(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Returns the last element of an observable sequence that satisfies the condition in the predicate, or a default value if no such element exists. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// A predicate function to evaluate for elements in the source sequence. + /// Sequence containing the last element in the observable sequence that satisfies the condition in the predicate, or a default value if no such element exists. + /// + /// or is null. + public static IQbservable LastOrDefaultAsync(this IQbservable source, Expression> predicate) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (predicate == null) + throw new ArgumentNullException(nameof(predicate)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + predicate + ) + ); + } + + /// + /// Returns an enumerable sequence whose enumeration returns the latest observed element in the source observable sequence. + /// Enumerators on the resulting sequence will never produce the same element repeatedly, and will block until the next element becomes available. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// The enumerable sequence that returns the last sampled element upon each iteration and subsequently blocks until the next element in the observable source sequence becomes available. + /// This operator requires the source's object (see ) to implement . + public static IQueryable Latest(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return ((IQueryProvider)source.Provider).CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Returns an observable sequence containing an that represents the total number of elements in an observable sequence. + /// + /// The type of the elements in the source sequence. + /// An observable sequence that contains elements to be counted. + /// An observable sequence containing a single element with the number of elements in the input sequence. + /// + /// is null. + /// (Asynchronous) The number of elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable LongCount(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Returns an observable sequence containing an that represents how many elements in the specified observable sequence satisfy a condition. + /// + /// The type of the elements in the source sequence. + /// An observable sequence that contains elements to be counted. + /// A function to test each element for a condition. + /// An observable sequence containing a single element with a number that represents how many elements in the input sequence satisfy the condition in the predicate function. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable LongCount(this IQbservable source, Expression> predicate) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (predicate == null) + throw new ArgumentNullException(nameof(predicate)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + predicate + ) + ); + } + + /// + /// Materializes the implicit notifications of an observable sequence as explicit notification values. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to get notification values for. + /// An observable sequence containing the materialized notification values from the source sequence. + /// + /// is null. + public static IQbservable> Materialize(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Returns the maximum value in an observable sequence of values. + /// + /// A sequence of values to determine the maximum value of. + /// An observable sequence containing a single element with the maximum value in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Max(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Returns the maximum value in an observable sequence of values. + /// + /// A sequence of values to determine the maximum value of. + /// An observable sequence containing a single element with the maximum value in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Max(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Returns the maximum value in an observable sequence of values. + /// + /// A sequence of values to determine the maximum value of. + /// An observable sequence containing a single element with the maximum value in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Max(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Returns the maximum value in an observable sequence of values. + /// + /// A sequence of values to determine the maximum value of. + /// An observable sequence containing a single element with the maximum value in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Max(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Returns the maximum value in an observable sequence of nullable values. + /// + /// A sequence of nullable values to determine the maximum value of. + /// An observable sequence containing a single element with the maximum value in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Max(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Returns the maximum value in an observable sequence of nullable values. + /// + /// A sequence of nullable values to determine the maximum value of. + /// An observable sequence containing a single element with the maximum value in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Max(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Returns the maximum value in an observable sequence of nullable values. + /// + /// A sequence of nullable values to determine the maximum value of. + /// An observable sequence containing a single element with the maximum value in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Max(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Returns the maximum value in an observable sequence of nullable values. + /// + /// A sequence of nullable values to determine the maximum value of. + /// An observable sequence containing a single element with the maximum value in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Max(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Returns the maximum value in an observable sequence of nullable values. + /// + /// A sequence of nullable values to determine the maximum value of. + /// An observable sequence containing a single element with the maximum value in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Max(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Returns the maximum value in an observable sequence of values. + /// + /// A sequence of values to determine the maximum value of. + /// An observable sequence containing a single element with the maximum value in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Max(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Returns the maximum element in an observable sequence. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to determine the maximum element of. + /// An observable sequence containing a single element with the maximum element in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Max(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Returns the maximum value in an observable sequence according to the specified comparer. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to determine the maximum element of. + /// Comparer used to compare elements. + /// An observable sequence containing a single element with the maximum element in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Max(this IQbservable source, IComparer comparer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(comparer, typeof(IComparer)) + ) + ); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the maximum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the maximum value in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Max(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the maximum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the maximum value in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Max(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the maximum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the maximum value in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Max(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the maximum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the maximum value in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Max(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the maximum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the maximum value in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Max(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum nullable value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the maximum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the maximum value in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Max(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum nullable value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the maximum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the maximum value in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Max(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum nullable value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the maximum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the maximum value in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Max(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum nullable value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the maximum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the maximum value in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Max(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum nullable value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the maximum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the maximum value in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Max(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum value. + /// + /// The type of the elements in the source sequence. + /// The type of the objects derived from the elements in the source sequence to determine the maximum of. + /// An observable sequence to determine the minimum element of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value that corresponds to the maximum element in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Max(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector + ) + ); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the maximum value according to the specified comparer. + /// + /// The type of the elements in the source sequence. + /// The type of the objects derived from the elements in the source sequence to determine the maximum of. + /// An observable sequence to determine the minimum element of. + /// A transform function to apply to each element. + /// Comparer used to compare elements. + /// An observable sequence containing a single element with the value that corresponds to the maximum element in the source sequence. + /// + /// or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Max(this IQbservable source, Expression> selector, IComparer comparer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector, + Expression.Constant(comparer, typeof(IComparer)) + ) + ); + } + + /// + /// Returns the elements in an observable sequence with the maximum key value. + /// + /// The type of the elements in the source sequence. + /// The type of the key computed for each element in the source sequence. + /// An observable sequence to get the maximum elements for. + /// Key selector function. + /// An observable sequence containing a list of zero or more elements that have a maximum key value. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable> MaxBy(this IQbservable source, Expression> keySelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey)), + source.Expression, + keySelector + ) + ); + } + + /// + /// Returns the elements in an observable sequence with the maximum key value according to the specified comparer. + /// + /// The type of the elements in the source sequence. + /// The type of the key computed for each element in the source sequence. + /// An observable sequence to get the maximum elements for. + /// Key selector function. + /// Comparer used to compare key values. + /// An observable sequence containing a list of zero or more elements that have a maximum key value. + /// + /// or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable> MaxBy(this IQbservable source, Expression> keySelector, IComparer comparer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey)), + source.Expression, + keySelector, + Expression.Constant(comparer, typeof(IComparer)) + ) + ); + } + + /// + /// Merges elements from two observable sequences into a single observable sequence. + /// + /// The type of the elements in the source sequences. + /// First observable sequence. + /// Second observable sequence. + /// The observable sequence that merges the elements of the given sequences. + /// + /// or is null. + public static IQbservable Merge(this IQbservable first, IObservable second) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + + return first.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + first.Expression, + GetSourceExpression(second) + ) + ); + } + + /// + /// Merges elements from two observable sequences into a single observable sequence, using the specified scheduler for enumeration of and subscription to the sources. + /// + /// The type of the elements in the source sequences. + /// First observable sequence. + /// Second observable sequence. + /// Scheduler used to introduce concurrency for making subscriptions to the given sequences. + /// The observable sequence that merges the elements of the given sequences. + /// + /// or or is null. + public static IQbservable Merge(this IQbservable first, IObservable second, IScheduler scheduler) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return first.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + first.Expression, + GetSourceExpression(second), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Merges elements from all of the specified observable sequences into a single observable sequence, using the specified scheduler for enumeration of and subscription to the sources. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequences. + /// Observable sequences. + /// Scheduler to run the enumeration of the sequence of sources on. + /// The observable sequence that merges the elements of the observable sequences. + /// + /// or is null. + public static IQbservable Merge(this IQbservableProvider provider, IScheduler scheduler, params IObservable[] sources) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(scheduler, typeof(IScheduler)), + GetSourceExpression(sources) + ) + ); + } + + /// + /// Merges elements from all inner observable sequences into a single observable sequence. + /// + /// The type of the elements in the source sequences. + /// Observable sequence of inner observable sequences. + /// The observable sequence that merges the elements of the inner sequences. + /// + /// is null. + public static IQbservable Merge(this IQbservable> sources) + { + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return sources.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + sources.Expression + ) + ); + } + + /// + /// Merges results from all source tasks into a single observable sequence. + /// + /// The type of the results produced by the source tasks. + /// Observable sequence of tasks. + /// The observable sequence that merges the results of the source tasks. + /// + /// is null. + /// If the tasks support cancellation, consider manual conversion of the tasks using , followed by a merge operation using . + public static IQbservable Merge(this IQbservable> sources) + { + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return sources.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + sources.Expression + ) + ); + } + + /// + /// Merges elements from all of the specified observable sequences into a single observable sequence. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequences. + /// Observable sequences. + /// The observable sequence that merges the elements of the observable sequences. + /// + /// is null. + public static IQbservable Merge(this IQbservableProvider provider, params IObservable[] sources) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + GetSourceExpression(sources) + ) + ); + } + + /// + /// Merges elements from all observable sequences in the given enumerable sequence into a single observable sequence. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequences. + /// Enumerable sequence of observable sequences. + /// The observable sequence that merges the elements of the observable sequences. + /// + /// is null. + public static IQbservable Merge(this IQbservableProvider provider, IEnumerable> sources) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + GetSourceExpression(sources) + ) + ); + } + + /// + /// Merges elements from all inner observable sequences into a single observable sequence, limiting the number of concurrent subscriptions to inner sequences. + /// + /// The type of the elements in the source sequences. + /// Observable sequence of inner observable sequences. + /// Maximum number of inner observable sequences being subscribed to concurrently. + /// The observable sequence that merges the elements of the inner sequences. + /// + /// is null. + /// + /// is less than or equal to zero. + public static IQbservable Merge(this IQbservable> sources, int maxConcurrent) + { + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return sources.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + sources.Expression, + Expression.Constant(maxConcurrent, typeof(int)) + ) + ); + } + + /// + /// Merges elements from all observable sequences in the given enumerable sequence into a single observable sequence, limiting the number of concurrent subscriptions to inner sequences. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequences. + /// Enumerable sequence of observable sequences. + /// Maximum number of observable sequences being subscribed to concurrently. + /// The observable sequence that merges the elements of the observable sequences. + /// + /// is null. + /// + /// is less than or equal to zero. + public static IQbservable Merge(this IQbservableProvider provider, IEnumerable> sources, int maxConcurrent) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + GetSourceExpression(sources), + Expression.Constant(maxConcurrent, typeof(int)) + ) + ); + } + + /// + /// Merges elements from all observable sequences in the given enumerable sequence into a single observable sequence, limiting the number of concurrent subscriptions to inner sequences, and using the specified scheduler for enumeration of and subscription to the sources. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequences. + /// Enumerable sequence of observable sequences. + /// Maximum number of observable sequences being subscribed to concurrently. + /// Scheduler to run the enumeration of the sequence of sources on. + /// The observable sequence that merges the elements of the observable sequences. + /// + /// or is null. + /// + /// is less than or equal to zero. + public static IQbservable Merge(this IQbservableProvider provider, IEnumerable> sources, int maxConcurrent, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + GetSourceExpression(sources), + Expression.Constant(maxConcurrent, typeof(int)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Merges elements from all observable sequences in the given enumerable sequence into a single observable sequence, using the specified scheduler for enumeration of and subscription to the sources. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequences. + /// Enumerable sequence of observable sequences. + /// Scheduler to run the enumeration of the sequence of sources on. + /// The observable sequence that merges the elements of the observable sequences. + /// + /// or is null. + public static IQbservable Merge(this IQbservableProvider provider, IEnumerable> sources, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + GetSourceExpression(sources), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Returns the minimum value in an observable sequence of values. + /// + /// A sequence of values to determine the minimum value of. + /// An observable sequence containing a single element with the minimum value in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Min(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Returns the minimum value in an observable sequence of values. + /// + /// A sequence of values to determine the minimum value of. + /// An observable sequence containing a single element with the minimum value in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Min(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Returns the minimum value in an observable sequence of values. + /// + /// A sequence of values to determine the minimum value of. + /// An observable sequence containing a single element with the minimum value in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Min(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Returns the minimum value in an observable sequence of values. + /// + /// A sequence of values to determine the minimum value of. + /// An observable sequence containing a single element with the minimum value in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Min(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Returns the minimum value in an observable sequence of nullable values. + /// + /// A sequence of nullable values to determine the minimum value of. + /// An observable sequence containing a single element with the minimum value in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Min(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Returns the minimum value in an observable sequence of nullable values. + /// + /// A sequence of nullable values to determine the minimum value of. + /// An observable sequence containing a single element with the minimum value in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Min(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Returns the minimum value in an observable sequence of nullable values. + /// + /// A sequence of nullable values to determine the minimum value of. + /// An observable sequence containing a single element with the minimum value in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Min(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Returns the minimum value in an observable sequence of nullable values. + /// + /// A sequence of nullable values to determine the minimum value of. + /// An observable sequence containing a single element with the minimum value in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Min(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Returns the minimum value in an observable sequence of nullable values. + /// + /// A sequence of nullable values to determine the minimum value of. + /// An observable sequence containing a single element with the minimum value in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Min(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Returns the minimum value in an observable sequence of values. + /// + /// A sequence of values to determine the minimum value of. + /// An observable sequence containing a single element with the minimum value in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Min(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Returns the minimum element in an observable sequence. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to determine the minimum element of. + /// An observable sequence containing a single element with the minimum element in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Min(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Returns the minimum element in an observable sequence according to the specified comparer. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to determine the minimum element of. + /// Comparer used to compare elements. + /// An observable sequence containing a single element with the minimum element in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Min(this IQbservable source, IComparer comparer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(comparer, typeof(IComparer)) + ) + ); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the minimum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the minimum value in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Min(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the minimum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the minimum value in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Min(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the minimum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the minimum value in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Min(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the minimum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the minimum value in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Min(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the minimum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the minimum value in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Min(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum nullable value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the minimum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the minimum value in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Min(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum nullable value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the minimum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the minimum value in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Min(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum nullable value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the minimum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the minimum value in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Min(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum nullable value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the minimum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the minimum value in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Min(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum nullable value. + /// + /// The type of the elements in the source sequence. + /// A sequence of values to determine the minimum value of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value of type that corresponds to the minimum value in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Min(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum value. + /// + /// The type of the elements in the source sequence. + /// The type of the objects derived from the elements in the source sequence to determine the minimum of. + /// An observable sequence to determine the minimum element of. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the value that corresponds to the minimum element in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Min(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector + ) + ); + } + + /// + /// Invokes a transform function on each element of a sequence and returns the minimum value according to the specified comparer. + /// + /// The type of the elements in the source sequence. + /// The type of the objects derived from the elements in the source sequence to determine the minimum of. + /// An observable sequence to determine the minimum element of. + /// A transform function to apply to each element. + /// Comparer used to compare elements. + /// An observable sequence containing a single element with the value that corresponds to the minimum element in the source sequence. + /// + /// or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Min(this IQbservable source, Expression> selector, IComparer comparer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector, + Expression.Constant(comparer, typeof(IComparer)) + ) + ); + } + + /// + /// Returns the elements in an observable sequence with the minimum key value. + /// + /// The type of the elements in the source sequence. + /// The type of the key computed for each element in the source sequence. + /// An observable sequence to get the minimum elements for. + /// Key selector function. + /// An observable sequence containing a list of zero or more elements that have a minimum key value. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable> MinBy(this IQbservable source, Expression> keySelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey)), + source.Expression, + keySelector + ) + ); + } + + /// + /// Returns the elements in an observable sequence with the minimum key value according to the specified comparer. + /// + /// The type of the elements in the source sequence. + /// The type of the key computed for each element in the source sequence. + /// An observable sequence to get the minimum elements for. + /// Key selector function. + /// Comparer used to compare key values. + /// An observable sequence containing a list of zero or more elements that have a minimum key value. + /// + /// or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable> MinBy(this IQbservable source, Expression> keySelector, IComparer comparer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey)), + source.Expression, + keySelector, + Expression.Constant(comparer, typeof(IComparer)) + ) + ); + } + + /// + /// Returns an enumerable sequence whose enumeration returns the most recently observed element in the source observable sequence, using the specified initial value in case no element has been sampled yet. + /// Enumerators on the resulting sequence never block and can produce the same element repeatedly. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// Initial value that will be yielded by the enumerable sequence if no element has been sampled yet. + /// The enumerable sequence that returns the last sampled element upon each iteration. + /// + /// is null. + /// This operator requires the source's object (see ) to implement . + public static IQueryable MostRecent(this IQbservable source, TSource initialValue) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return ((IQueryProvider)source.Provider).CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(initialValue, typeof(TSource)) + ) + ); + } + + /// + /// Multicasts the source sequence notifications through an instantiated subject into all uses of the sequence within a selector function. Each + /// subscription to the resulting sequence causes a separate multicast invocation, exposing the sequence resulting from the selector function's + /// invocation. For specializations with fixed subject types, see Publish, PublishLast, and Replay. + /// + /// The type of the elements in the source sequence. + /// The type of the elements produced by the intermediate subject. + /// The type of the elements in the result sequence. + /// Source sequence which will be multicasted in the specified selector function. + /// Factory function to create an intermediate subject through which the source sequence's elements will be multicast to the selector function. + /// Selector function which can use the multicasted source sequence subject to the policies enforced by the created subject. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// + /// or or is null. + public static IQbservable Multicast(this IQbservable source, Expression>> subjectSelector, Expression, IObservable>> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (subjectSelector == null) + throw new ArgumentNullException(nameof(subjectSelector)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TIntermediate), typeof(TResult)), + source.Expression, + subjectSelector, + selector + ) + ); + } + + /// + /// Returns a non-terminating observable sequence, which can be used to denote an infinite duration (e.g. when using reactive joins). + /// + /// Query provider used to construct the data source. + /// The type used for the type parameter of the resulting sequence. + /// An observable sequence whose observers will never get called. + public static IQbservable Never(this IQbservableProvider provider) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)) + ) + ); + } + + /// + /// Returns a non-terminating observable sequence, which can be used to denote an infinite duration (e.g. when using reactive joins). + /// + /// Query provider used to construct the data source. + /// The type used for the type parameter of the resulting sequence. + /// Object solely used to infer the type of the type parameter. This parameter is typically used when creating a sequence of anonymously typed elements. + /// An observable sequence whose observers will never get called. + public static IQbservable Never(this IQbservableProvider provider, TResult witness) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(witness, typeof(TResult)) + ) + ); + } + + /// + /// Returns an enumerable sequence whose enumeration blocks until the next element in the source observable sequence becomes available. + /// Enumerators on the resulting sequence will block until the next element becomes available. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// The enumerable sequence that blocks upon each iteration until the next element in the observable source sequence becomes available. + /// + /// is null. + /// This operator requires the source's object (see ) to implement . + public static IQueryable Next(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return ((IQueryProvider)source.Provider).CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Wraps the source sequence in order to run its observer callbacks on the specified synchronization context. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Synchronization context to notify observers on. + /// The source sequence whose observations happen on the specified synchronization context. + /// + /// or is null. + /// + /// This only invokes observer callbacks on a synchronization context. In case the subscription and/or unsubscription actions have side-effects + /// that require to be run on a synchronization context, use . + /// + public static IQbservable ObserveOn(this IQbservable source, SynchronizationContext context) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (context == null) + throw new ArgumentNullException(nameof(context)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(context, typeof(SynchronizationContext)) + ) + ); + } + + /// + /// Wraps the source sequence in order to run its observer callbacks on the specified scheduler. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Scheduler to notify observers on. + /// The source sequence whose observations happen on the specified scheduler. + /// + /// or is null. + /// + /// This only invokes observer callbacks on a scheduler. In case the subscription and/or unsubscription actions have side-effects + /// that require to be run on a scheduler, use . + /// + public static IQbservable ObserveOn(this IQbservable source, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Filters the elements of an observable sequence based on the specified type. + /// + /// The type to filter the elements in the source sequence on. + /// The observable sequence that contains the elements to be filtered. + /// An observable sequence that contains elements from the input sequence of type TResult. + /// + /// is null. + public static IQbservable OfType(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + source.Expression + ) + ); + } + + /// + /// Concatenates the second observable sequence to the first observable sequence upon successful or exceptional termination of the first. + /// + /// The type of the elements in the source sequences. + /// First observable sequence whose exception (if any) is caught. + /// Second observable sequence used to produce results after the first sequence terminates. + /// An observable sequence that concatenates the first and second sequence, even if the first sequence terminates exceptionally. + /// + /// or is null. + public static IQbservable OnErrorResumeNext(this IQbservable first, IObservable second) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + + return first.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + first.Expression, + GetSourceExpression(second) + ) + ); + } + + /// + /// Concatenates all of the specified observable sequences, even if the previous observable sequence terminated exceptionally. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequences. + /// Observable sequences to concatenate. + /// An observable sequence that concatenates the source sequences, even if a sequence terminates exceptionally. + /// + /// is null. + public static IQbservable OnErrorResumeNext(this IQbservableProvider provider, params IObservable[] sources) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + GetSourceExpression(sources) + ) + ); + } + + /// + /// Concatenates all observable sequences in the given enumerable sequence, even if the previous observable sequence terminated exceptionally. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequences. + /// Observable sequences to concatenate. + /// An observable sequence that concatenates the source sequences, even if a sequence terminates exceptionally. + /// + /// is null. + public static IQbservable OnErrorResumeNext(this IQbservableProvider provider, IEnumerable> sources) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + GetSourceExpression(sources) + ) + ); + } + + /// + /// Prepend a value to an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to prepend the value to. + /// Value to prepend to the specified sequence. + /// The source sequence prepended with the specified value. + /// + /// is null. + public static IQbservable Prepend(this IQbservable source, TSource value) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(value, typeof(TSource)) + ) + ); + } + + /// + /// Prepend a value to an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to prepend the value to. + /// Value to prepend to the specified sequence. + /// Scheduler to emit the prepend values on. + /// The source sequence prepended with the specified value. + /// + /// is null. + public static IQbservable Prepend(this IQbservable source, TSource value, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(value, typeof(TSource)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Returns an observable sequence that is the result of invoking the selector on a connectable observable sequence that shares a single subscription to the underlying sequence. + /// This operator is a specialization of Multicast using a regular . + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive all notifications of the source from the time of the subscription on. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// + /// or is null. + /// + public static IQbservable Publish(this IQbservable source, Expression, IObservable>> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector + ) + ); + } + + /// + /// Returns an observable sequence that is the result of invoking the selector on a connectable observable sequence that shares a single subscription to the underlying sequence and starts with initialValue. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive immediately receive the initial value, followed by all notifications of the source from the time of the subscription on. + /// Initial value received by observers upon subscription. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// + /// or is null. + /// + public static IQbservable Publish(this IQbservable source, Expression, IObservable>> selector, TSource initialValue) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector, + Expression.Constant(initialValue, typeof(TSource)) + ) + ); + } + + /// + /// Returns an observable sequence that is the result of invoking the selector on a connectable observable sequence that shares a single subscription to the underlying sequence containing only the last notification. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will only receive the last notification of the source. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// + /// or is null. + /// + public static IQbservable PublishLast(this IQbservable source, Expression, IObservable>> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector + ) + ); + } + + /// + /// Generates an observable sequence of integral numbers within a specified range. + /// + /// Query provider used to construct the data source. + /// The value of the first integer in the sequence. + /// The number of sequential integers to generate. + /// An observable sequence that contains a range of sequential integral numbers. + /// + /// is less than zero. -or- + - 1 is larger than . + public static IQbservable Range(this IQbservableProvider provider, int start, int count) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(start, typeof(int)), + Expression.Constant(count, typeof(int)) + ) + ); + } + + /// + /// Generates an observable sequence of integral numbers within a specified range, using the specified scheduler to send out observer messages. + /// + /// Query provider used to construct the data source. + /// The value of the first integer in the sequence. + /// The number of sequential integers to generate. + /// Scheduler to run the generator loop on. + /// An observable sequence that contains a range of sequential integral numbers. + /// + /// is less than zero. -or- + - 1 is larger than . + /// + /// is null. + public static IQbservable Range(this IQbservableProvider provider, int start, int count, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(start, typeof(int)), + Expression.Constant(count, typeof(int)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Returns an observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequence. + /// Connectable observable sequence. + /// An observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. + /// + /// is null. + public static IQbservable RefCount(this IQbservableProvider provider, IConnectableObservable source) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(source, typeof(IConnectableObservable)) + ) + ); + } + + /// + /// Returns an observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequence. + /// Connectable observable sequence. + /// The time span that should be waited before possibly unsubscribing from the connectable observable. + /// An observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. + /// + /// is null. + public static IQbservable RefCount(this IQbservableProvider provider, IConnectableObservable source, TimeSpan disconnectDelay) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(source, typeof(IConnectableObservable)), + Expression.Constant(disconnectDelay, typeof(TimeSpan)) + ) + ); + } + + /// + /// Returns an observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequence. + /// Connectable observable sequence. + /// The time span that should be waited before possibly unsubscribing from the connectable observable. + /// The scheduler to use for delayed unsubscription. + /// An observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. + /// + /// is null. + public static IQbservable RefCount(this IQbservableProvider provider, IConnectableObservable source, TimeSpan disconnectDelay, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(source, typeof(IConnectableObservable)), + Expression.Constant(disconnectDelay, typeof(TimeSpan)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Returns an observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequence. + /// Connectable observable sequence. + /// The minimum number of observers required to subscribe before establishing the connection to the source. + /// An observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. + /// + /// is null. + /// is non-positive. + public static IQbservable RefCount(this IQbservableProvider provider, IConnectableObservable source, int minObservers) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(source, typeof(IConnectableObservable)), + Expression.Constant(minObservers, typeof(int)) + ) + ); + } + + /// + /// Returns an observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequence. + /// Connectable observable sequence. + /// The minimum number of observers required to subscribe before establishing the connection to the source. + /// The time span that should be waited before possibly unsubscribing from the connectable observable. + /// An observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. + /// + /// is null. + /// is non-positive. + public static IQbservable RefCount(this IQbservableProvider provider, IConnectableObservable source, int minObservers, TimeSpan disconnectDelay) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(source, typeof(IConnectableObservable)), + Expression.Constant(minObservers, typeof(int)), + Expression.Constant(disconnectDelay, typeof(TimeSpan)) + ) + ); + } + + /// + /// Returns an observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequence. + /// Connectable observable sequence. + /// The minimum number of observers required to subscribe before establishing the connection to the source. + /// The time span that should be waited before possibly unsubscribing from the connectable observable. + /// The scheduler to use for delayed unsubscription. + /// An observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. + /// + /// is null. + /// is non-positive. + public static IQbservable RefCount(this IQbservableProvider provider, IConnectableObservable source, int minObservers, TimeSpan disconnectDelay, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(source, typeof(IConnectableObservable)), + Expression.Constant(minObservers, typeof(int)), + Expression.Constant(disconnectDelay, typeof(TimeSpan)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Generates an observable sequence that repeats the given element infinitely. + /// + /// Query provider used to construct the data source. + /// The type of the element that will be repeated in the produced sequence. + /// Element to repeat. + /// An observable sequence that repeats the given element infinitely. + public static IQbservable Repeat(this IQbservableProvider provider, TResult value) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(value, typeof(TResult)) + ) + ); + } + + /// + /// Generates an observable sequence that repeats the given element the specified number of times. + /// + /// Query provider used to construct the data source. + /// The type of the element that will be repeated in the produced sequence. + /// Element to repeat. + /// Number of times to repeat the element. + /// An observable sequence that repeats the given element the specified number of times. + /// + /// is less than zero. + public static IQbservable Repeat(this IQbservableProvider provider, TResult value, int repeatCount) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(value, typeof(TResult)), + Expression.Constant(repeatCount, typeof(int)) + ) + ); + } + + /// + /// Generates an observable sequence that repeats the given element the specified number of times, using the specified scheduler to send out observer messages. + /// + /// Query provider used to construct the data source. + /// The type of the element that will be repeated in the produced sequence. + /// Element to repeat. + /// Number of times to repeat the element. + /// Scheduler to run the producer loop on. + /// An observable sequence that repeats the given element the specified number of times. + /// + /// is less than zero. + /// + /// is null. + public static IQbservable Repeat(this IQbservableProvider provider, TResult value, int repeatCount, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(value, typeof(TResult)), + Expression.Constant(repeatCount, typeof(int)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Generates an observable sequence that repeats the given element infinitely, using the specified scheduler to send out observer messages. + /// + /// Query provider used to construct the data source. + /// The type of the element that will be repeated in the produced sequence. + /// Element to repeat. + /// Scheduler to run the producer loop on. + /// An observable sequence that repeats the given element infinitely. + /// + /// is null. + public static IQbservable Repeat(this IQbservableProvider provider, TResult value, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(value, typeof(TResult)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Repeats the observable sequence indefinitely. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to repeat. + /// The observable sequence producing the elements of the given sequence repeatedly and sequentially. + /// + /// is null. + public static IQbservable Repeat(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Repeats the observable sequence a specified number of times. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to repeat. + /// Number of times to repeat the sequence. + /// The observable sequence producing the elements of the given sequence repeatedly. + /// + /// is null. + /// + /// is less than zero. + public static IQbservable Repeat(this IQbservable source, int repeatCount) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(repeatCount, typeof(int)) + ) + ); + } + + /// + /// Repeatedly resubscribes to the source observable after a normal completion and when the observable + /// returned by a handler produces an arbitrary item. + /// + /// The type of the elements in the source sequence. + /// The arbitrary element type signaled by the handler observable. + /// Observable sequence to keep repeating when it successfully terminates. + /// The function that is called for each observer and takes an observable sequence of objects. + /// It should return an observable of arbitrary items that should signal that arbitrary item in + /// response to receiving the completion signal from the source observable. If this observable signals + /// a terminal event, the sequence is terminated with that signal instead. + /// An observable sequence producing the elements of the given sequence repeatedly while each repetition terminates successfully. + /// is null. + /// is null. + public static IQbservable RepeatWhen(this IQbservable source, Expression, IObservable>> handler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (handler == null) + throw new ArgumentNullException(nameof(handler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TSignal)), + source.Expression, + handler + ) + ); + } + + /// + /// Returns an observable sequence that is the result of invoking the selector on a connectable observable sequence that shares a single subscription to the underlying sequence replaying all notifications. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive all the notifications of the source. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// + /// or is null. + /// + public static IQbservable Replay(this IQbservable source, Expression, IObservable>> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector + ) + ); + } + + /// + /// Returns an observable sequence that is the result of invoking the selector on a connectable observable sequence that shares a single subscription to the underlying sequence replaying notifications subject to a maximum element count for the replay buffer. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive all the notifications of the source subject to the specified replay buffer trimming policy. + /// Maximum element count of the replay buffer. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// + /// or is null. + /// + /// is less than zero. + /// + public static IQbservable Replay(this IQbservable source, Expression, IObservable>> selector, int bufferSize) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector, + Expression.Constant(bufferSize, typeof(int)) + ) + ); + } + + /// + /// Returns an observable sequence that is the result of invoking the selector on a connectable observable sequence that shares a single subscription to the underlying sequence replaying notifications subject to a maximum element count for the replay buffer. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive all the notifications of the source subject to the specified replay buffer trimming policy. + /// Maximum element count of the replay buffer. + /// Scheduler where connected observers within the selector function will be invoked on. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// + /// or or is null. + /// + /// is less than zero. + /// + public static IQbservable Replay(this IQbservable source, Expression, IObservable>> selector, int bufferSize, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector, + Expression.Constant(bufferSize, typeof(int)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Returns an observable sequence that is the result of invoking the selector on a connectable observable sequence that shares a single subscription to the underlying sequence replaying notifications subject to a maximum time length and element count for the replay buffer. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive all the notifications of the source subject to the specified replay buffer trimming policy. + /// Maximum element count of the replay buffer. + /// Maximum time length of the replay buffer. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// + /// or is null. + /// + /// is less than zero. + /// + /// is less than TimeSpan.Zero. + /// + public static IQbservable Replay(this IQbservable source, Expression, IObservable>> selector, int bufferSize, TimeSpan window) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector, + Expression.Constant(bufferSize, typeof(int)), + Expression.Constant(window, typeof(TimeSpan)) + ) + ); + } + + /// + /// Returns an observable sequence that is the result of invoking the selector on a connectable observable sequence that shares a single subscription to the underlying sequence replaying notifications subject to a maximum time length and element count for the replay buffer. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive all the notifications of the source subject to the specified replay buffer trimming policy. + /// Maximum element count of the replay buffer. + /// Maximum time length of the replay buffer. + /// Scheduler where connected observers within the selector function will be invoked on. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// + /// or or is null. + /// + /// is less than zero. + /// + /// is less than TimeSpan.Zero. + /// + public static IQbservable Replay(this IQbservable source, Expression, IObservable>> selector, int bufferSize, TimeSpan window, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector, + Expression.Constant(bufferSize, typeof(int)), + Expression.Constant(window, typeof(TimeSpan)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Returns an observable sequence that is the result of invoking the selector on a connectable observable sequence that shares a single subscription to the underlying sequence replaying all notifications. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive all the notifications of the source. + /// Scheduler where connected observers within the selector function will be invoked on. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// + /// or or is null. + /// + public static IQbservable Replay(this IQbservable source, Expression, IObservable>> selector, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Returns an observable sequence that is the result of invoking the selector on a connectable observable sequence that shares a single subscription to the underlying sequence replaying notifications subject to a maximum time length for the replay buffer. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive all the notifications of the source subject to the specified replay buffer trimming policy. + /// Maximum time length of the replay buffer. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// + /// or is null. + /// + /// is less than TimeSpan.Zero. + /// + public static IQbservable Replay(this IQbservable source, Expression, IObservable>> selector, TimeSpan window) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector, + Expression.Constant(window, typeof(TimeSpan)) + ) + ); + } + + /// + /// Returns an observable sequence that is the result of invoking the selector on a connectable observable sequence that shares a single subscription to the underlying sequence replaying notifications subject to a maximum time length for the replay buffer. + /// This operator is a specialization of Multicast using a . + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence whose elements will be multicasted through a single shared subscription. + /// Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive all the notifications of the source subject to the specified replay buffer trimming policy. + /// Maximum time length of the replay buffer. + /// Scheduler where connected observers within the selector function will be invoked on. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// + /// or or is null. + /// + /// is less than TimeSpan.Zero. + /// + public static IQbservable Replay(this IQbservable source, Expression, IObservable>> selector, TimeSpan window, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector, + Expression.Constant(window, typeof(TimeSpan)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Repeats the source observable sequence until it successfully terminates. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to repeat until it successfully terminates. + /// An observable sequence producing the elements of the given sequence repeatedly until it terminates successfully. + /// + /// is null. + public static IQbservable Retry(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Repeats the source observable sequence the specified number of times or until it successfully terminates. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to repeat until it successfully terminates. + /// Number of times to repeat the sequence. + /// An observable sequence producing the elements of the given sequence repeatedly until it terminates successfully. + /// + /// is null. + /// + /// is less than zero. + public static IQbservable Retry(this IQbservable source, int retryCount) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(retryCount, typeof(int)) + ) + ); + } + + /// + /// Retries (resubscribes to) the source observable after a failure and when the observable + /// returned by a handler produces an arbitrary item. + /// + /// The type of the elements in the source sequence. + /// The arbitrary element type signaled by the handler observable. + /// Observable sequence to repeat until it successfully terminates. + /// The function that is called for each observer and takes an observable sequence of + /// errors. It should return an observable of arbitrary items that should signal that arbitrary item in + /// response to receiving the failure Exception from the source observable. If this observable signals + /// a terminal event, the sequence is terminated with that signal instead. + /// An observable sequence producing the elements of the given sequence repeatedly until it terminates successfully. + /// + /// is null. + /// + /// is null. + public static IQbservable RetryWhen(this IQbservable source, Expression, IObservable>> handler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (handler == null) + throw new ArgumentNullException(nameof(handler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TSignal)), + source.Expression, + handler + ) + ); + } + + /// + /// Returns an observable sequence that contains a single element. + /// + /// Query provider used to construct the data source. + /// The type of the element that will be returned in the produced sequence. + /// Single element in the resulting observable sequence. + /// An observable sequence containing the single specified element. + public static IQbservable Return(this IQbservableProvider provider, TResult value) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(value, typeof(TResult)) + ) + ); + } + + /// + /// Returns an observable sequence that contains a single element, using the specified scheduler to send out observer messages. + /// + /// Query provider used to construct the data source. + /// The type of the element that will be returned in the produced sequence. + /// Single element in the resulting observable sequence. + /// Scheduler to send the single element on. + /// An observable sequence containing the single specified element. + /// + /// is null. + public static IQbservable Return(this IQbservableProvider provider, TResult value, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(value, typeof(TResult)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Samples the observable sequence at each interval. + /// Upon each sampling tick, the latest element (if any) in the source sequence during the last sampling interval is sent to the resulting sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to sample. + /// Interval at which to sample. If this value is equal to TimeSpan.Zero, the scheduler will continuously sample the stream. + /// Sampled observable sequence. + /// + /// is null. + /// + /// is less than TimeSpan.Zero. + /// + /// Specifying a TimeSpan.Zero value for doesn't guarantee all source sequence elements will be preserved. This is a side-effect + /// of the asynchrony introduced by the scheduler, where the sampling action may not execute immediately, despite the TimeSpan.Zero due time. + /// + public static IQbservable Sample(this IQbservable source, TimeSpan interval) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(interval, typeof(TimeSpan)) + ) + ); + } + + /// + /// Samples the observable sequence at each interval, using the specified scheduler to run sampling timers. + /// Upon each sampling tick, the latest element (if any) in the source sequence during the last sampling interval is sent to the resulting sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to sample. + /// Interval at which to sample. If this value is equal to TimeSpan.Zero, the scheduler will continuously sample the stream. + /// Scheduler to run the sampling timer on. + /// Sampled observable sequence. + /// + /// or is null. + /// + /// is less than TimeSpan.Zero. + /// + /// Specifying a TimeSpan.Zero value for doesn't guarantee all source sequence elements will be preserved. This is a side-effect + /// of the asynchrony introduced by the scheduler, where the sampling action may not execute immediately, despite the TimeSpan.Zero due time. + /// + public static IQbservable Sample(this IQbservable source, TimeSpan interval, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(interval, typeof(TimeSpan)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Samples the source observable sequence using a sampler observable sequence producing sampling ticks. + /// Upon each sampling tick, the latest element (if any) in the source sequence during the last sampling interval is sent to the resulting sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the sampling sequence. + /// Source sequence to sample. + /// Sampling tick sequence. + /// Sampled observable sequence. + /// + /// or is null. + public static IQbservable Sample(this IQbservable source, IObservable sampler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (sampler == null) + throw new ArgumentNullException(nameof(sampler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TSample)), + source.Expression, + GetSourceExpression(sampler) + ) + ); + } + + /// + /// Applies an accumulator function over an observable sequence and returns each intermediate result. + /// For aggregation behavior with no intermediate results, see . + /// + /// The type of the elements in the source sequence and the result of the aggregation. + /// An observable sequence to accumulate over. + /// An accumulator function to be invoked on each element. + /// An observable sequence containing the accumulated values. + /// + /// or is null. + public static IQbservable Scan(this IQbservable source, Expression> accumulator) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (accumulator == null) + throw new ArgumentNullException(nameof(accumulator)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + accumulator + ) + ); + } + + /// + /// Applies an accumulator function over an observable sequence and returns each intermediate result. The specified seed value is used as the initial accumulator value. + /// For aggregation behavior with no intermediate results, see . + /// + /// The type of the elements in the source sequence. + /// The type of the result of the aggregation. + /// An observable sequence to accumulate over. + /// The initial accumulator value. + /// An accumulator function to be invoked on each element. + /// An observable sequence containing the accumulated values. + /// + /// or is null. + public static IQbservable Scan(this IQbservable source, TAccumulate seed, Expression> accumulator) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (accumulator == null) + throw new ArgumentNullException(nameof(accumulator)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TAccumulate)), + source.Expression, + Expression.Constant(seed, typeof(TAccumulate)), + accumulator + ) + ); + } + + /// + /// Projects each element of an observable sequence into a new form. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence, obtained by running the selector function for each element in the source sequence. + /// A sequence of elements to invoke a transform function on. + /// A transform function to apply to each source element. + /// An observable sequence whose elements are the result of invoking the transform function on each element of source. + /// + /// or is null. + public static IQbservable Select(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector + ) + ); + } + + /// + /// Projects each element of an observable sequence into a new form by incorporating the element's index. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence, obtained by running the selector function for each element in the source sequence. + /// A sequence of elements to invoke a transform function on. + /// A transform function to apply to each source element; the second parameter of the function represents the index of the source element. + /// An observable sequence whose elements are the result of invoking the transform function on each element of source. + /// + /// or is null. + public static IQbservable Select(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector + ) + ); + } + + /// + /// Projects each element of an observable sequence to an observable sequence, invokes the result selector for the source element and each of the corresponding inner sequence's elements, and merges the results into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the projected intermediate sequences. + /// The type of the elements in the result sequence, obtained by using the selector to combine source sequence elements with their corresponding intermediate sequence elements. + /// An observable sequence of elements to project. + /// A transform function to apply to each element. + /// A transform function to apply to each element of the intermediate sequence. + /// An observable sequence whose elements are the result of invoking the one-to-many transform function collectionSelector on each element of the input sequence and then mapping each of those sequence elements and their corresponding source element to a result element. + /// + /// or or is null. + public static IQbservable SelectMany(this IQbservable source, Expression>> collectionSelector, Expression> resultSelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (collectionSelector == null) + throw new ArgumentNullException(nameof(collectionSelector)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TCollection), typeof(TResult)), + source.Expression, + collectionSelector, + resultSelector + ) + ); + } + + /// + /// Projects each element of an observable sequence to an observable sequence by incorporating the element's index, invokes the result selector for the source element and each of the corresponding inner sequence's elements, and merges the results into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the projected intermediate sequences. + /// The type of the elements in the result sequence, obtained by using the selector to combine source sequence elements with their corresponding intermediate sequence elements. + /// An observable sequence of elements to project. + /// A transform function to apply to each element; the second parameter of the function represents the index of the source element. + /// A transform function to apply to each element of the intermediate sequence; the second parameter of the function represents the index of the source element and the fourth parameter represents the index of the intermediate element. + /// An observable sequence whose elements are the result of invoking the one-to-many transform function collectionSelector on each element of the input sequence and then mapping each of those sequence elements and their corresponding source element to a result element. + /// + /// or or is null. + public static IQbservable SelectMany(this IQbservable source, Expression>> collectionSelector, Expression> resultSelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (collectionSelector == null) + throw new ArgumentNullException(nameof(collectionSelector)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TCollection), typeof(TResult)), + source.Expression, + collectionSelector, + resultSelector + ) + ); + } + + /// + /// Projects each element of an observable sequence to an enumerable sequence, invokes the result selector for the source element and each of the corresponding inner sequence's elements, and merges the results into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the projected intermediate enumerable sequences. + /// The type of the elements in the result sequence, obtained by using the selector to combine source sequence elements with their corresponding intermediate sequence elements. + /// An observable sequence of elements to project. + /// A transform function to apply to each element. + /// A transform function to apply to each element of the intermediate sequence. + /// An observable sequence whose elements are the result of invoking the one-to-many transform function collectionSelector on each element of the input sequence and then mapping each of those sequence elements and their corresponding source element to a result element. + /// + /// or or is null. + /// The projected sequences are enumerated synchronously within the OnNext call of the source sequence. In order to do a concurrent, non-blocking merge, change the selector to return an observable sequence obtained using the conversion. + public static IQbservable SelectMany(this IQbservable source, Expression>> collectionSelector, Expression> resultSelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (collectionSelector == null) + throw new ArgumentNullException(nameof(collectionSelector)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TCollection), typeof(TResult)), + source.Expression, + collectionSelector, + resultSelector + ) + ); + } + + /// + /// Projects each element of an observable sequence to an enumerable sequence by incorporating the element's index, invokes the result selector for the source element and each of the corresponding inner sequence's elements, and merges the results into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the projected intermediate enumerable sequences. + /// The type of the elements in the result sequence, obtained by using the selector to combine source sequence elements with their corresponding intermediate sequence elements. + /// An observable sequence of elements to project. + /// A transform function to apply to each element; the second parameter of the function represents the index of the source element. + /// A transform function to apply to each element of the intermediate sequence; the second parameter of the function represents the index of the source element and the fourth parameter represents the index of the intermediate element. + /// An observable sequence whose elements are the result of invoking the one-to-many transform function collectionSelector on each element of the input sequence and then mapping each of those sequence elements and their corresponding source element to a result element. + /// + /// or or is null. + /// The projected sequences are enumerated synchronously within the OnNext call of the source sequence. In order to do a concurrent, non-blocking merge, change the selector to return an observable sequence obtained using the conversion. + public static IQbservable SelectMany(this IQbservable source, Expression>> collectionSelector, Expression> resultSelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (collectionSelector == null) + throw new ArgumentNullException(nameof(collectionSelector)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TCollection), typeof(TResult)), + source.Expression, + collectionSelector, + resultSelector + ) + ); + } + + /// + /// Projects each element of the source observable sequence to the other observable sequence and merges the resulting observable sequences into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the other sequence and the elements in the result sequence. + /// An observable sequence of elements to project. + /// An observable sequence to project each element from the source sequence onto. + /// An observable sequence whose elements are the result of projecting each source element onto the other sequence and merging all the resulting sequences together. + /// + /// or is null. + public static IQbservable SelectMany(this IQbservable source, IObservable other) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (other == null) + throw new ArgumentNullException(nameof(other)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TOther)), + source.Expression, + GetSourceExpression(other) + ) + ); + } + + /// + /// Projects each notification of an observable sequence to an observable sequence and merges the resulting observable sequences into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the projected inner sequences and the elements in the merged result sequence. + /// An observable sequence of notifications to project. + /// A transform function to apply to each element. + /// A transform function to apply when an error occurs in the source sequence. + /// A transform function to apply when the end of the source sequence is reached. + /// An observable sequence whose elements are the result of invoking the one-to-many transform function corresponding to each notification in the input sequence. + /// + /// or or or is null. + public static IQbservable SelectMany(this IQbservable source, Expression>> onNext, Expression>> onError, Expression>> onCompleted) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (onNext == null) + throw new ArgumentNullException(nameof(onNext)); + if (onError == null) + throw new ArgumentNullException(nameof(onError)); + if (onCompleted == null) + throw new ArgumentNullException(nameof(onCompleted)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + onNext, + onError, + onCompleted + ) + ); + } + + /// + /// Projects each notification of an observable sequence to an observable sequence by incorporating the element's index and merges the resulting observable sequences into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the projected inner sequences and the elements in the merged result sequence. + /// An observable sequence of notifications to project. + /// A transform function to apply to each element; the second parameter of the function represents the index of the source element. + /// A transform function to apply when an error occurs in the source sequence. + /// A transform function to apply when the end of the source sequence is reached. + /// An observable sequence whose elements are the result of invoking the one-to-many transform function corresponding to each notification in the input sequence. + /// + /// or or or is null. + public static IQbservable SelectMany(this IQbservable source, Expression>> onNext, Expression>> onError, Expression>> onCompleted) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (onNext == null) + throw new ArgumentNullException(nameof(onNext)); + if (onError == null) + throw new ArgumentNullException(nameof(onError)); + if (onCompleted == null) + throw new ArgumentNullException(nameof(onCompleted)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + onNext, + onError, + onCompleted + ) + ); + } + + /// + /// Projects each element of an observable sequence to an observable sequence and merges the resulting observable sequences into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the projected inner sequences and the elements in the merged result sequence. + /// An observable sequence of elements to project. + /// A transform function to apply to each element. + /// An observable sequence whose elements are the result of invoking the one-to-many transform function on each element of the input sequence. + /// + /// or is null. + public static IQbservable SelectMany(this IQbservable source, Expression>> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector + ) + ); + } + + /// + /// Projects each element of an observable sequence to an observable sequence by incorporating the element's index and merges the resulting observable sequences into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the projected inner sequences and the elements in the merged result sequence. + /// An observable sequence of elements to project. + /// A transform function to apply to each element; the second parameter of the function represents the index of the source element. + /// An observable sequence whose elements are the result of invoking the one-to-many transform function on each element of the input sequence. + /// + /// or is null. + public static IQbservable SelectMany(this IQbservable source, Expression>> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector + ) + ); + } + + /// + /// Projects each element of an observable sequence to a task and merges all of the task results into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the result produced by the projected tasks and the elements in the merged result sequence. + /// An observable sequence of elements to project. + /// A transform function to apply to each element. + /// An observable sequence whose elements are the result of the tasks executed for each element of the input sequence. + /// This overload supports composition of observable sequences and tasks, without requiring manual conversion of the tasks to observable sequences using . + /// + /// or is null. + public static IQbservable SelectMany(this IQbservable source, Expression>> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector + ) + ); + } + + /// + /// Projects each element of an observable sequence to a task by incorporating the element's index and merges all of the task results into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the result produced by the projected tasks and the elements in the merged result sequence. + /// An observable sequence of elements to project. + /// A transform function to apply to each element; the second parameter of the function represents the index of the source element. + /// An observable sequence whose elements are the result of the tasks executed for each element of the input sequence. + /// This overload supports composition of observable sequences and tasks, without requiring manual conversion of the tasks to observable sequences using . + /// + /// or is null. + public static IQbservable SelectMany(this IQbservable source, Expression>> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector + ) + ); + } + + /// + /// Projects each element of an observable sequence to a task with cancellation support and merges all of the task results into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the result produced by the projected tasks and the elements in the merged result sequence. + /// An observable sequence of elements to project. + /// A transform function to apply to each element. + /// An observable sequence whose elements are the result of the tasks executed for each element of the input sequence. + /// This overload supports composition of observable sequences and tasks, without requiring manual conversion of the tasks to observable sequences using . + /// + /// or is null. + public static IQbservable SelectMany(this IQbservable source, Expression>> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector + ) + ); + } + + /// + /// Projects each element of an observable sequence to a task by incorporating the element's index with cancellation support and merges all of the task results into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the result produced by the projected tasks and the elements in the merged result sequence. + /// An observable sequence of elements to project. + /// A transform function to apply to each element; the second parameter of the function represents the index of the source element. + /// An observable sequence whose elements are the result of the tasks executed for each element of the input sequence. + /// This overload supports composition of observable sequences and tasks, without requiring manual conversion of the tasks to observable sequences using . + /// + /// or is null. + public static IQbservable SelectMany(this IQbservable source, Expression>> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector + ) + ); + } + + /// + /// Projects each element of an observable sequence to an enumerable sequence and concatenates the resulting enumerable sequences into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the projected inner enumerable sequences and the elements in the merged result sequence. + /// An observable sequence of elements to project. + /// A transform function to apply to each element. + /// An observable sequence whose elements are the result of invoking the one-to-many transform function on each element of the input sequence. + /// + /// or is null. + /// The projected sequences are enumerated synchronously within the OnNext call of the source sequence. In order to do a concurrent, non-blocking merge, change the selector to return an observable sequence obtained using the conversion. + public static IQbservable SelectMany(this IQbservable source, Expression>> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector + ) + ); + } + + /// + /// Projects each element of an observable sequence to an enumerable sequence by incorporating the element's index and concatenates the resulting enumerable sequences into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the projected inner enumerable sequences and the elements in the merged result sequence. + /// An observable sequence of elements to project. + /// A transform function to apply to each element; the second parameter of the function represents the index of the source element. + /// An observable sequence whose elements are the result of invoking the one-to-many transform function on each element of the input sequence. + /// + /// or is null. + /// The projected sequences are enumerated synchronously within the OnNext call of the source sequence. In order to do a concurrent, non-blocking merge, change the selector to return an observable sequence obtained using the conversion. + public static IQbservable SelectMany(this IQbservable source, Expression>> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector + ) + ); + } + + /// + /// Projects each element of an observable sequence to a task, invokes the result selector for the source element and the task result, and merges the results into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the results produced by the projected intermediate tasks. + /// The type of the elements in the result sequence, obtained by using the selector to combine source sequence elements with their corresponding intermediate task results. + /// An observable sequence of elements to project. + /// A transform function to apply to each element. + /// A transform function to apply to each element of the intermediate sequence. + /// An observable sequence whose elements are the result of obtaining a task for each element of the input sequence and then mapping the task's result and its corresponding source element to a result element. + /// + /// or or is null. + /// This overload supports using LINQ query comprehension syntax in C# and Visual Basic to compose observable sequences and tasks, without requiring manual conversion of the tasks to observable sequences using . + public static IQbservable SelectMany(this IQbservable source, Expression>> taskSelector, Expression> resultSelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (taskSelector == null) + throw new ArgumentNullException(nameof(taskSelector)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TTaskResult), typeof(TResult)), + source.Expression, + taskSelector, + resultSelector + ) + ); + } + + /// + /// Projects each element of an observable sequence to a task by incorporating the element's index, invokes the result selector for the source element and the task result, and merges the results into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the results produced by the projected intermediate tasks. + /// The type of the elements in the result sequence, obtained by using the selector to combine source sequence elements with their corresponding intermediate task results. + /// An observable sequence of elements to project. + /// A transform function to apply to each element; the second parameter of the function represents the index of the source element. + /// A transform function to apply to each element of the intermediate sequence; the second parameter of the function represents the index of the source element. + /// An observable sequence whose elements are the result of obtaining a task for each element of the input sequence and then mapping the task's result and its corresponding source element to a result element. + /// + /// or or is null. + /// This overload supports using LINQ query comprehension syntax in C# and Visual Basic to compose observable sequences and tasks, without requiring manual conversion of the tasks to observable sequences using . + public static IQbservable SelectMany(this IQbservable source, Expression>> taskSelector, Expression> resultSelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (taskSelector == null) + throw new ArgumentNullException(nameof(taskSelector)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TTaskResult), typeof(TResult)), + source.Expression, + taskSelector, + resultSelector + ) + ); + } + + /// + /// Projects each element of an observable sequence to a task with cancellation support, invokes the result selector for the source element and the task result, and merges the results into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the results produced by the projected intermediate tasks. + /// The type of the elements in the result sequence, obtained by using the selector to combine source sequence elements with their corresponding intermediate task results. + /// An observable sequence of elements to project. + /// A transform function to apply to each element. + /// A transform function to apply to each element of the intermediate sequence. + /// An observable sequence whose elements are the result of obtaining a task for each element of the input sequence and then mapping the task's result and its corresponding source element to a result element. + /// + /// or or is null. + /// This overload supports using LINQ query comprehension syntax in C# and Visual Basic to compose observable sequences and tasks, without requiring manual conversion of the tasks to observable sequences using . + public static IQbservable SelectMany(this IQbservable source, Expression>> taskSelector, Expression> resultSelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (taskSelector == null) + throw new ArgumentNullException(nameof(taskSelector)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TTaskResult), typeof(TResult)), + source.Expression, + taskSelector, + resultSelector + ) + ); + } + + /// + /// Projects each element of an observable sequence to a task by incorporating the element's index with cancellation support, invokes the result selector for the source element and the task result, and merges the results into one observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the results produced by the projected intermediate tasks. + /// The type of the elements in the result sequence, obtained by using the selector to combine source sequence elements with their corresponding intermediate task results. + /// An observable sequence of elements to project. + /// A transform function to apply to each element; the second parameter of the function represents the index of the source element. + /// A transform function to apply to each element of the intermediate sequence; the second parameter of the function represents the index of the source element. + /// An observable sequence whose elements are the result of obtaining a task for each element of the input sequence and then mapping the task's result and its corresponding source element to a result element. + /// + /// or or is null. + /// This overload supports using LINQ query comprehension syntax in C# and Visual Basic to compose observable sequences and tasks, without requiring manual conversion of the tasks to observable sequences using . + public static IQbservable SelectMany(this IQbservable source, Expression>> taskSelector, Expression> resultSelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (taskSelector == null) + throw new ArgumentNullException(nameof(taskSelector)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TTaskResult), typeof(TResult)), + source.Expression, + taskSelector, + resultSelector + ) + ); + } + + /// + /// Determines whether two sequences are equal by comparing the elements pairwise. + /// + /// The type of the elements in the source sequence. + /// First observable sequence to compare. + /// Second observable sequence to compare. + /// An observable sequence that contains a single element which indicates whether both sequences are of equal length and their corresponding elements are equal according to the default equality comparer for their type. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable SequenceEqual(this IQbservable first, IObservable second) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + + return first.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + first.Expression, + GetSourceExpression(second) + ) + ); + } + + /// + /// Determines whether an observable and enumerable sequence are equal by comparing the elements pairwise. + /// + /// The type of the elements in the source sequence. + /// First observable sequence to compare. + /// Second observable sequence to compare. + /// An observable sequence that contains a single element which indicates whether both sequences are of equal length and their corresponding elements are equal according to the default equality comparer for their type. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable SequenceEqual(this IQbservable first, IEnumerable second) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + + return first.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + first.Expression, + GetSourceExpression(second) + ) + ); + } + + /// + /// Determines whether two sequences are equal by comparing the elements pairwise using a specified equality comparer. + /// + /// The type of the elements in the source sequence. + /// First observable sequence to compare. + /// Second observable sequence to compare. + /// Comparer used to compare elements of both sequences. + /// An observable sequence that contains a single element which indicates whether both sequences are of equal length and their corresponding elements are equal according to the specified equality comparer. + /// + /// or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable SequenceEqual(this IQbservable first, IObservable second, IEqualityComparer comparer) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return first.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + first.Expression, + GetSourceExpression(second), + Expression.Constant(comparer, typeof(IEqualityComparer)) + ) + ); + } + + /// + /// Determines whether an observable and enumerable sequence are equal by comparing the elements pairwise using a specified equality comparer. + /// + /// The type of the elements in the source sequence. + /// First observable sequence to compare. + /// Second observable sequence to compare. + /// Comparer used to compare elements of both sequences. + /// An observable sequence that contains a single element which indicates whether both sequences are of equal length and their corresponding elements are equal according to the specified equality comparer. + /// + /// or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable SequenceEqual(this IQbservable first, IEnumerable second, IEqualityComparer comparer) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return first.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + first.Expression, + GetSourceExpression(second), + Expression.Constant(comparer, typeof(IEqualityComparer)) + ) + ); + } + + /// + /// Returns the only element of an observable sequence, and reports an exception if there is not exactly one element in the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// Sequence containing the single element in the observable sequence. + /// + /// is null. + /// (Asynchronous) The source sequence contains more than one element. -or- The source sequence is empty. + public static IQbservable SingleAsync(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Returns the only element of an observable sequence that satisfies the condition in the predicate, and reports an exception if there is not exactly one element in the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// A predicate function to evaluate for elements in the source sequence. + /// Sequence containing the single element in the observable sequence that satisfies the condition in the predicate. + /// + /// or is null. + /// (Asynchronous) No element satisfies the condition in the predicate. -or- More than one element satisfies the condition in the predicate. -or- The source sequence is empty. + public static IQbservable SingleAsync(this IQbservable source, Expression> predicate) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (predicate == null) + throw new ArgumentNullException(nameof(predicate)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + predicate + ) + ); + } + + /// + /// Returns the only element of an observable sequence, or a default value if the observable sequence is empty; this method reports an exception if there is more than one element in the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// Sequence containing the single element in the observable sequence, or a default value if no such element exists. + /// + /// is null. + /// (Asynchronous) The source sequence contains more than one element. + public static IQbservable SingleOrDefaultAsync(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Returns the only element of an observable sequence that matches the predicate, or a default value if no such element exists; this method reports an exception if there is more than one element in the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source observable sequence. + /// A predicate function to evaluate for elements in the source sequence. + /// Sequence containing the single element in the observable sequence that satisfies the condition in the predicate, or a default value if no such element exists. + /// + /// or is null. + /// (Asynchronous) The sequence contains more than one element that satisfies the condition in the predicate. + public static IQbservable SingleOrDefaultAsync(this IQbservable source, Expression> predicate) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (predicate == null) + throw new ArgumentNullException(nameof(predicate)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + predicate + ) + ); + } + + /// + /// Bypasses a specified number of elements in an observable sequence and then returns the remaining elements. + /// + /// The type of the elements in the source sequence. + /// The sequence to take elements from. + /// The number of elements to skip before returning the remaining elements. + /// An observable sequence that contains the elements that occur after the specified index in the input sequence. + /// + /// is null. + /// + /// is less than zero. + public static IQbservable Skip(this IQbservable source, int count) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(count, typeof(int)) + ) + ); + } + + /// + /// Skips elements for the specified duration from the start of the observable source sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to skip elements for. + /// Duration for skipping elements from the start of the sequence. + /// An observable sequence with the elements skipped during the specified duration from the start of the source sequence. + /// + /// is null. + /// + /// is less than TimeSpan.Zero. + /// + /// + /// Specifying a TimeSpan.Zero value for doesn't guarantee no elements will be dropped from the start of the source sequence. + /// This is a side-effect of the asynchrony introduced by the scheduler, where the action that causes callbacks from the source sequence to be forwarded + /// may not execute immediately, despite the TimeSpan.Zero due time. + /// + /// + /// Errors produced by the source sequence are always forwarded to the result sequence, even if the error occurs before the . + /// + /// + public static IQbservable Skip(this IQbservable source, TimeSpan duration) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(duration, typeof(TimeSpan)) + ) + ); + } + + /// + /// Skips elements for the specified duration from the start of the observable source sequence, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence. + /// Source sequence to skip elements for. + /// Duration for skipping elements from the start of the sequence. + /// Scheduler to run the timer on. + /// An observable sequence with the elements skipped during the specified duration from the start of the source sequence. + /// + /// or is null. + /// + /// is less than TimeSpan.Zero. + /// + /// + /// Specifying a TimeSpan.Zero value for doesn't guarantee no elements will be dropped from the start of the source sequence. + /// This is a side-effect of the asynchrony introduced by the scheduler, where the action that causes callbacks from the source sequence to be forwarded + /// may not execute immediately, despite the TimeSpan.Zero due time. + /// + /// + /// Errors produced by the source sequence are always forwarded to the result sequence, even if the error occurs before the . + /// + /// + public static IQbservable Skip(this IQbservable source, TimeSpan duration, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(duration, typeof(TimeSpan)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Bypasses a specified number of elements at the end of an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Number of elements to bypass at the end of the source sequence. + /// An observable sequence containing the source sequence elements except for the bypassed ones at the end. + /// + /// is null. + /// + /// is less than zero. + /// + /// This operator accumulates a queue with a length enough to store the first elements. As more elements are + /// received, elements are taken from the front of the queue and produced on the result sequence. This causes elements to be delayed. + /// + public static IQbservable SkipLast(this IQbservable source, int count) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(count, typeof(int)) + ) + ); + } + + /// + /// Skips elements for the specified duration from the end of the observable source sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to skip elements for. + /// Duration for skipping elements from the end of the sequence. + /// An observable sequence with the elements skipped during the specified duration from the end of the source sequence. + /// + /// is null. + /// + /// is less than TimeSpan.Zero. + /// + /// This operator accumulates a queue with a length enough to store elements received during the initial window. + /// As more elements are received, elements older than the specified are taken from the queue and produced on the + /// result sequence. This causes elements to be delayed with . + /// + public static IQbservable SkipLast(this IQbservable source, TimeSpan duration) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(duration, typeof(TimeSpan)) + ) + ); + } + + /// + /// Skips elements for the specified duration from the end of the observable source sequence, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence. + /// Source sequence to skip elements for. + /// Duration for skipping elements from the end of the sequence. + /// Scheduler to run the timer on. + /// An observable sequence with the elements skipped during the specified duration from the end of the source sequence. + /// + /// or is null. + /// + /// is less than TimeSpan.Zero. + /// + /// This operator accumulates a queue with a length enough to store elements received during the initial window. + /// As more elements are received, elements older than the specified are taken from the queue and produced on the + /// result sequence. This causes elements to be delayed with . + /// + public static IQbservable SkipLast(this IQbservable source, TimeSpan duration, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(duration, typeof(TimeSpan)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Skips elements from the observable source sequence until the specified start time. + /// + /// The type of the elements in the source sequence. + /// Source sequence to skip elements for. + /// Time to start taking elements from the source sequence. If this value is less than or equal to DateTimeOffset.UtcNow, no elements will be skipped. + /// An observable sequence with the elements skipped until the specified start time. + /// + /// is null. + /// + /// Errors produced by the source sequence are always forwarded to the result sequence, even if the error occurs before the . + /// + public static IQbservable SkipUntil(this IQbservable source, DateTimeOffset startTime) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(startTime, typeof(DateTimeOffset)) + ) + ); + } + + /// + /// Skips elements from the observable source sequence until the specified start time, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence. + /// Source sequence to skip elements for. + /// Time to start taking elements from the source sequence. If this value is less than or equal to DateTimeOffset.UtcNow, no elements will be skipped. + /// Scheduler to run the timer on. + /// An observable sequence with the elements skipped until the specified start time. + /// + /// or is null. + /// + /// Errors produced by the source sequence are always forwarded to the result sequence, even if the error occurs before the . + /// + public static IQbservable SkipUntil(this IQbservable source, DateTimeOffset startTime, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(startTime, typeof(DateTimeOffset)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Returns the elements from the source observable sequence only after the other observable sequence produces an element. + /// Starting from Rx.NET 4.0, this will subscribe to before subscribing to + /// so in case emits an element right away, elements from are not missed. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the other sequence that indicates the end of skip behavior. + /// Source sequence to propagate elements for. + /// Observable sequence that triggers propagation of elements of the source sequence. + /// An observable sequence containing the elements of the source sequence starting from the point the other sequence triggered propagation. + /// + /// or is null. + public static IQbservable SkipUntil(this IQbservable source, IObservable other) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (other == null) + throw new ArgumentNullException(nameof(other)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TOther)), + source.Expression, + GetSourceExpression(other) + ) + ); + } + + /// + /// Bypasses elements in an observable sequence as long as a specified condition is true and then returns the remaining elements. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to return elements from. + /// A function to test each element for a condition. + /// An observable sequence that contains the elements from the input sequence starting at the first element in the linear series that does not pass the test specified by predicate. + /// + /// or is null. + public static IQbservable SkipWhile(this IQbservable source, Expression> predicate) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (predicate == null) + throw new ArgumentNullException(nameof(predicate)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + predicate + ) + ); + } + + /// + /// Bypasses elements in an observable sequence as long as a specified condition is true and then returns the remaining elements. + /// The element's index is used in the logic of the predicate function. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to return elements from. + /// A function to test each element for a condition; the second parameter of the function represents the index of the source element. + /// An observable sequence that contains the elements from the input sequence starting at the first element in the linear series that does not pass the test specified by predicate. + /// + /// or is null. + public static IQbservable SkipWhile(this IQbservable source, Expression> predicate) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (predicate == null) + throw new ArgumentNullException(nameof(predicate)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + predicate + ) + ); + } + + /// + /// Invokes the action asynchronously, surfacing the result through an observable sequence. + /// + /// Query provider used to construct the data source. + /// Action to run asynchronously. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// + /// is null. + /// + /// + /// + /// The action is called immediately, not during the subscription of the resulting sequence. + /// + /// + /// Multiple subscriptions to the resulting sequence can observe the action's outcome. + /// + /// + /// + public static IQbservable Start(this IQbservableProvider provider, Expression action) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + action + ) + ); + } + + /// + /// Invokes the action asynchronously on the specified scheduler, surfacing the result through an observable sequence. + /// + /// Query provider used to construct the data source. + /// Action to run asynchronously. + /// Scheduler to run the action on. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// + /// or is null. + /// + /// + /// + /// The action is called immediately, not during the subscription of the resulting sequence. + /// + /// + /// Multiple subscriptions to the resulting sequence can observe the action's outcome. + /// + /// + /// + public static IQbservable Start(this IQbservableProvider provider, Expression action, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + action, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Invokes the specified function asynchronously, surfacing the result through an observable sequence. + /// + /// Query provider used to construct the data source. + /// The type of the result returned by the function. + /// Function to run asynchronously. + /// An observable sequence exposing the function's result value, or an exception. + /// + /// is null. + /// + /// + /// + /// The function is called immediately, not during the subscription of the resulting sequence. + /// + /// + /// Multiple subscriptions to the resulting sequence can observe the function's result. + /// + /// + /// + public static IQbservable Start(this IQbservableProvider provider, Expression> function) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + function + ) + ); + } + + /// + /// Invokes the specified function asynchronously on the specified scheduler, surfacing the result through an observable sequence + /// + /// Query provider used to construct the data source. + /// The type of the result returned by the function. + /// Function to run asynchronously. + /// Scheduler to run the function on. + /// An observable sequence exposing the function's result value, or an exception. + /// + /// or is null. + /// + /// + /// + /// The function is called immediately, not during the subscription of the resulting sequence. + /// + /// + /// Multiple subscriptions to the resulting sequence can observe the function's result. + /// + /// + /// + public static IQbservable Start(this IQbservableProvider provider, Expression> function, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + function, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Invokes the asynchronous action, surfacing the result through an observable sequence. + /// + /// Query provider used to construct the data source. + /// Asynchronous action to run. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// + /// is null. + /// + /// + /// + /// The action is started immediately, not during the subscription of the resulting sequence. + /// + /// + /// Multiple subscriptions to the resulting sequence can observe the action's outcome. + /// + /// + /// + public static IQbservable StartAsync(this IQbservableProvider provider, Expression> actionAsync) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (actionAsync == null) + throw new ArgumentNullException(nameof(actionAsync)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + actionAsync + ) + ); + } + + /// + /// Invokes the asynchronous action, surfacing the result through an observable sequence. + /// + /// Query provider used to construct the data source. + /// Asynchronous action to run. + /// Controls how the tasks's progress is observed. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// + /// is null. + /// + /// + /// + /// The action is started immediately, not during the subscription of the resulting sequence. + /// + /// + /// Multiple subscriptions to the resulting sequence can observe the action's outcome. + /// + /// + /// + public static IQbservable StartAsync(this IQbservableProvider provider, Expression> actionAsync, TaskObservationOptions options) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (actionAsync == null) + throw new ArgumentNullException(nameof(actionAsync)); + if (options == null) + throw new ArgumentNullException(nameof(options)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + actionAsync, + Expression.Constant(options, typeof(TaskObservationOptions)) + ) + ); + } + + /// + /// Invokes the asynchronous action, surfacing the result through an observable sequence. + /// + /// Query provider used to construct the data source. + /// Asynchronous action to run. + /// Scheduler on which to notify observers. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// + /// is null or is null. + /// + /// + /// + /// The action is started immediately, not during the subscription of the resulting sequence. + /// + /// + /// Multiple subscriptions to the resulting sequence can observe the action's outcome. + /// + /// + /// + public static IQbservable StartAsync(this IQbservableProvider provider, Expression> actionAsync, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (actionAsync == null) + throw new ArgumentNullException(nameof(actionAsync)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + actionAsync, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Invokes the asynchronous action, surfacing the result through an observable sequence. + /// The CancellationToken is shared by all subscriptions on the resulting observable sequence. See the remarks section for more information. + /// + /// Query provider used to construct the data source. + /// Asynchronous action to run. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// + /// is null. + /// + /// + /// + /// The action is started immediately, not during the subscription of the resulting sequence. + /// + /// + /// Multiple subscriptions to the resulting sequence can observe the action's outcome. + /// + /// + /// + /// If any subscription to the resulting sequence is disposed, the CancellationToken is set. The observer associated to the disposed + /// subscription won't see the TaskCanceledException, but other observers will. You can protect against this using the Catch operator. + /// Be careful when handing out the resulting sequence because of this behavior. The most common use is to have a single subscription + /// to the resulting sequence, which controls the CancellationToken state. Alternatively, you can control subscription behavior using + /// multicast operators. + /// + /// + /// + /// + public static IQbservable StartAsync(this IQbservableProvider provider, Expression> actionAsync) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (actionAsync == null) + throw new ArgumentNullException(nameof(actionAsync)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + actionAsync + ) + ); + } + + /// + /// Invokes the asynchronous action, surfacing the result through an observable sequence. + /// The CancellationToken is shared by all subscriptions on the resulting observable sequence. See the remarks section for more information. + /// + /// Query provider used to construct the data source. + /// Asynchronous action to run. + /// Controls how the tasks's progress is observed. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// + /// is null. + /// + /// + /// + /// The action is started immediately, not during the subscription of the resulting sequence. + /// + /// + /// Multiple subscriptions to the resulting sequence can observe the action's outcome. + /// + /// + /// + /// If any subscription to the resulting sequence is disposed, the CancellationToken is set. The observer associated to the disposed + /// subscription won't see the TaskCanceledException, but other observers will. You can protect against this using the Catch operator. + /// Be careful when handing out the resulting sequence because of this behavior. The most common use is to have a single subscription + /// to the resulting sequence, which controls the CancellationToken state. Alternatively, you can control subscription behavior using + /// multicast operators. + /// + /// + /// + /// + public static IQbservable StartAsync(this IQbservableProvider provider, Expression> actionAsync, TaskObservationOptions options) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (actionAsync == null) + throw new ArgumentNullException(nameof(actionAsync)); + if (options == null) + throw new ArgumentNullException(nameof(options)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + actionAsync, + Expression.Constant(options, typeof(TaskObservationOptions)) + ) + ); + } + + /// + /// Invokes the asynchronous action, surfacing the result through an observable sequence. + /// The CancellationToken is shared by all subscriptions on the resulting observable sequence. See the remarks section for more information. + /// + /// Query provider used to construct the data source. + /// Asynchronous action to run. + /// Scheduler on which to notify observers. + /// An observable sequence exposing a Unit value upon completion of the action, or an exception. + /// + /// is null or is null. + /// + /// + /// + /// The action is started immediately, not during the subscription of the resulting sequence. + /// + /// + /// Multiple subscriptions to the resulting sequence can observe the action's outcome. + /// + /// + /// + /// If any subscription to the resulting sequence is disposed, the CancellationToken is set. The observer associated to the disposed + /// subscription won't see the TaskCanceledException, but other observers will. You can protect against this using the Catch operator. + /// Be careful when handing out the resulting sequence because of this behavior. The most common use is to have a single subscription + /// to the resulting sequence, which controls the CancellationToken state. Alternatively, you can control subscription behavior using + /// multicast operators. + /// + /// + /// + /// + public static IQbservable StartAsync(this IQbservableProvider provider, Expression> actionAsync, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (actionAsync == null) + throw new ArgumentNullException(nameof(actionAsync)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + actionAsync, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Invokes the asynchronous function, surfacing the result through an observable sequence. + /// + /// Query provider used to construct the data source. + /// The type of the result returned by the asynchronous function. + /// Asynchronous function to run. + /// An observable sequence exposing the function's result value, or an exception. + /// + /// is null. + /// + /// + /// + /// The function is started immediately, not during the subscription of the resulting sequence. + /// + /// + /// Multiple subscriptions to the resulting sequence can observe the function's result. + /// + /// + /// + public static IQbservable StartAsync(this IQbservableProvider provider, Expression>> functionAsync) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (functionAsync == null) + throw new ArgumentNullException(nameof(functionAsync)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + functionAsync + ) + ); + } + + /// + /// Invokes the asynchronous function, surfacing the result through an observable sequence. + /// The CancellationToken is shared by all subscriptions on the resulting observable sequence. See the remarks section for more information. + /// + /// Query provider used to construct the data source. + /// The type of the result returned by the asynchronous function. + /// Asynchronous function to run. + /// An observable sequence exposing the function's result value, or an exception. + /// + /// is null. + /// + /// + /// + /// The function is started immediately, not during the subscription of the resulting sequence. + /// + /// + /// Multiple subscriptions to the resulting sequence can observe the function's result. + /// + /// + /// + /// If any subscription to the resulting sequence is disposed, the CancellationToken is set. The observer associated to the disposed + /// subscription won't see the TaskCanceledException, but other observers will. You can protect against this using the Catch operator. + /// Be careful when handing out the resulting sequence because of this behavior. The most common use is to have a single subscription + /// to the resulting sequence, which controls the CancellationToken state. Alternatively, you can control subscription behavior using + /// multicast operators. + /// + /// + /// + /// + public static IQbservable StartAsync(this IQbservableProvider provider, Expression>> functionAsync) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (functionAsync == null) + throw new ArgumentNullException(nameof(functionAsync)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + functionAsync + ) + ); + } + + /// + /// Invokes the asynchronous function, surfacing the result through an observable sequence. + /// The CancellationToken is shared by all subscriptions on the resulting observable sequence. See the remarks section for more information. + /// + /// Query provider used to construct the data source. + /// The type of the result returned by the asynchronous function. + /// Asynchronous function to run. + /// Controls how the tasks's progress is observed. + /// An observable sequence exposing the function's result value, or an exception. + /// + /// is null. + /// + /// + /// + /// The function is started immediately, not during the subscription of the resulting sequence. + /// + /// + /// Multiple subscriptions to the resulting sequence can observe the function's result. + /// + /// + /// + /// If any subscription to the resulting sequence is disposed, the CancellationToken is set. The observer associated to the disposed + /// subscription won't see the TaskCanceledException, but other observers will. You can protect against this using the Catch operator. + /// Be careful when handing out the resulting sequence because of this behavior. The most common use is to have a single subscription + /// to the resulting sequence, which controls the CancellationToken state. Alternatively, you can control subscription behavior using + /// multicast operators. + /// + /// + /// + /// + public static IQbservable StartAsync(this IQbservableProvider provider, Expression>> functionAsync, TaskObservationOptions options) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (functionAsync == null) + throw new ArgumentNullException(nameof(functionAsync)); + if (options == null) + throw new ArgumentNullException(nameof(options)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + functionAsync, + Expression.Constant(options, typeof(TaskObservationOptions)) + ) + ); + } + + /// + /// Invokes the asynchronous function, surfacing the result through an observable sequence. + /// + /// Query provider used to construct the data source. + /// The type of the result returned by the asynchronous function. + /// Asynchronous function to run. + /// Controls how the tasks's progress is observed. + /// An observable sequence exposing the function's result value, or an exception. + /// + /// is null. + /// + /// + /// + /// The function is started immediately, not during the subscription of the resulting sequence. + /// + /// + /// Multiple subscriptions to the resulting sequence can observe the function's result. + /// + /// + /// + public static IQbservable StartAsync(this IQbservableProvider provider, Expression>> functionAsync, TaskObservationOptions options) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (functionAsync == null) + throw new ArgumentNullException(nameof(functionAsync)); + if (options == null) + throw new ArgumentNullException(nameof(options)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + functionAsync, + Expression.Constant(options, typeof(TaskObservationOptions)) + ) + ); + } + + /// + /// Invokes the asynchronous function, surfacing the result through an observable sequence. + /// The CancellationToken is shared by all subscriptions on the resulting observable sequence. See the remarks section for more information. + /// + /// Query provider used to construct the data source. + /// The type of the result returned by the asynchronous function. + /// Asynchronous function to run. + /// Scheduler on which to notify observers. + /// An observable sequence exposing the function's result value, or an exception. + /// + /// is null or is null. + /// + /// + /// + /// The function is started immediately, not during the subscription of the resulting sequence. + /// + /// + /// Multiple subscriptions to the resulting sequence can observe the function's result. + /// + /// + /// + /// If any subscription to the resulting sequence is disposed, the CancellationToken is set. The observer associated to the disposed + /// subscription won't see the TaskCanceledException, but other observers will. You can protect against this using the Catch operator. + /// Be careful when handing out the resulting sequence because of this behavior. The most common use is to have a single subscription + /// to the resulting sequence, which controls the CancellationToken state. Alternatively, you can control subscription behavior using + /// multicast operators. + /// + /// + /// + /// + public static IQbservable StartAsync(this IQbservableProvider provider, Expression>> functionAsync, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (functionAsync == null) + throw new ArgumentNullException(nameof(functionAsync)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + functionAsync, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Invokes the asynchronous function, surfacing the result through an observable sequence. + /// + /// Query provider used to construct the data source. + /// The type of the result returned by the asynchronous function. + /// Asynchronous function to run. + /// Scheduler on which to notify observers. + /// An observable sequence exposing the function's result value, or an exception. + /// + /// is null or is null. + /// + /// + /// + /// The function is started immediately, not during the subscription of the resulting sequence. + /// + /// + /// Multiple subscriptions to the resulting sequence can observe the function's result. + /// + /// + /// + public static IQbservable StartAsync(this IQbservableProvider provider, Expression>> functionAsync, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (functionAsync == null) + throw new ArgumentNullException(nameof(functionAsync)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + functionAsync, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Prepends a sequence of values to an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to prepend values to. + /// Scheduler to emit the prepended values on. + /// Values to prepend to the specified sequence. + /// The source sequence prepended with the specified values. + /// + /// or or is null. + public static IQbservable StartWith(this IQbservable source, IScheduler scheduler, params TSource[] values) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + if (values == null) + throw new ArgumentNullException(nameof(values)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(scheduler, typeof(IScheduler)), + Expression.Constant(values, typeof(TSource[])) + ) + ); + } + + /// + /// Prepends a sequence of values to an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to prepend values to. + /// Scheduler to emit the prepended values on. + /// Values to prepend to the specified sequence. + /// The source sequence prepended with the specified values. + /// + /// or or is null. + public static IQbservable StartWith(this IQbservable source, IScheduler scheduler, IEnumerable values) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + if (values == null) + throw new ArgumentNullException(nameof(values)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(scheduler, typeof(IScheduler)), + GetSourceExpression(values) + ) + ); + } + + /// + /// Prepends a sequence of values to an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to prepend values to. + /// Values to prepend to the specified sequence. + /// The source sequence prepended with the specified values. + /// + /// or is null. + public static IQbservable StartWith(this IQbservable source, params TSource[] values) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (values == null) + throw new ArgumentNullException(nameof(values)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(values, typeof(TSource[])) + ) + ); + } + + /// + /// Prepends a sequence of values to an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to prepend values to. + /// Values to prepend to the specified sequence. + /// The source sequence prepended with the specified values. + /// + /// or is null. + public static IQbservable StartWith(this IQbservable source, IEnumerable values) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (values == null) + throw new ArgumentNullException(nameof(values)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + GetSourceExpression(values) + ) + ); + } + + /// + /// Wraps the source sequence in order to run its subscription and unsubscription logic on the specified synchronization context. This operation is not commonly used; + /// see the remarks section for more information on the distinction between SubscribeOn and ObserveOn. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Synchronization context to perform subscription and unsubscription actions on. + /// The source sequence whose subscriptions and unsubscriptions happen on the specified synchronization context. + /// + /// or is null. + /// + /// This only performs the side-effects of subscription and unsubscription on the specified synchronization context. In order to invoke observer + /// callbacks on a synchronization context, use . + /// + public static IQbservable SubscribeOn(this IQbservable source, SynchronizationContext context) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (context == null) + throw new ArgumentNullException(nameof(context)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(context, typeof(SynchronizationContext)) + ) + ); + } + + /// + /// Wraps the source sequence in order to run its subscription and unsubscription logic on the specified scheduler. This operation is not commonly used; + /// see the remarks section for more information on the distinction between SubscribeOn and ObserveOn. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Scheduler to perform subscription and unsubscription actions on. + /// The source sequence whose subscriptions and unsubscriptions happen on the specified scheduler. + /// + /// or is null. + /// + /// This only performs the side-effects of subscription and unsubscription on the specified scheduler. In order to invoke observer + /// callbacks on a scheduler, use . + /// + public static IQbservable SubscribeOn(this IQbservable source, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Computes the sum of a sequence of values. + /// + /// A sequence of values to calculate the sum of. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// + /// is null. + /// (Asynchronous) The sum of the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Sum(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Computes the sum of a sequence of values. + /// + /// A sequence of values to calculate the sum of. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Sum(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Computes the sum of a sequence of values. + /// + /// A sequence of values to calculate the sum of. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// + /// is null. + /// (Asynchronous) The sum of the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Sum(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Computes the sum of a sequence of values. + /// + /// A sequence of values to calculate the sum of. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// + /// is null. + /// (Asynchronous) The sum of the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Sum(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Computes the sum of a sequence of nullable values. + /// + /// A sequence of nullable values to calculate the sum of. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// + /// is null. + /// (Asynchronous) The sum of the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Sum(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Computes the sum of a sequence of nullable values. + /// + /// A sequence of nullable values to calculate the sum of. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Sum(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Computes the sum of a sequence of nullable values. + /// + /// A sequence of nullable values to calculate the sum of. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// + /// is null. + /// (Asynchronous) The sum of the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Sum(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Computes the sum of a sequence of nullable values. + /// + /// A sequence of nullable values to calculate the sum of. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// + /// is null. + /// (Asynchronous) The sum of the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Sum(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Computes the sum of a sequence of nullable values. + /// + /// A sequence of nullable values to calculate the sum of. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Sum(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Computes the sum of a sequence of values. + /// + /// A sequence of values to calculate the sum of. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Sum(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + source.Expression + ) + ); + } + + /// + /// Computes the sum of a sequence of values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values that are used to calculate a sum. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Sum(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Computes the sum of a sequence of values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values that are used to calculate a sum. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Sum(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Computes the sum of a sequence of values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values that are used to calculate a sum. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// + /// or is null. + /// (Asynchronous) The sum of the projected values for the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Sum(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Computes the sum of a sequence of values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values that are used to calculate a sum. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// + /// or is null. + /// (Asynchronous) The sum of the projected values for the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Sum(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Computes the sum of a sequence of values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values that are used to calculate a sum. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// + /// or is null. + /// (Asynchronous) The sum of the projected values for the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Sum(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Computes the sum of a sequence of nullable values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values that are used to calculate a sum. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Sum(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Computes the sum of a sequence of nullable values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values that are used to calculate a sum. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Sum(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Computes the sum of a sequence of nullable values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values that are used to calculate a sum. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// + /// or is null. + /// (Asynchronous) The sum of the projected values for the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Sum(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Computes the sum of a sequence of nullable values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values that are used to calculate a sum. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// + /// or is null. + /// (Asynchronous) The sum of the projected values for the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Sum(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Computes the sum of a sequence of nullable values that are obtained by invoking a transform function on each element of the input sequence. + /// + /// The type of the elements in the source sequence. + /// A sequence of values that are used to calculate a sum. + /// A transform function to apply to each element. + /// An observable sequence containing a single element with the sum of the values in the source sequence. + /// + /// or is null. + /// (Asynchronous) The sum of the projected values for the elements in the source sequence is larger than . + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable Sum(this IQbservable source, Expression> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } + + /// + /// Transforms an observable sequence of observable sequences into an observable sequence + /// producing values only from the most recent observable sequence. + /// Each time a new inner observable sequence is received, unsubscribe from the + /// previous inner observable sequence. + /// + /// The type of the elements in the source sequences. + /// Observable sequence of inner observable sequences. + /// The observable sequence that at any point in time produces the elements of the most recent inner observable sequence that has been received. + /// + /// is null. + public static IQbservable Switch(this IQbservable> sources) + { + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return sources.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + sources.Expression + ) + ); + } + + /// + /// Transforms an observable sequence of tasks into an observable sequence + /// producing values only from the most recent observable sequence. + /// Each time a new task is received, the previous task's result is ignored. + /// + /// The type of the results produced by the source tasks. + /// Observable sequence of tasks. + /// The observable sequence that at any point in time produces the result of the most recent task that has been received. + /// + /// is null. + /// If the tasks support cancellation, consider manual conversion of the tasks using , followed by a switch operation using . + public static IQbservable Switch(this IQbservable> sources) + { + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return sources.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + sources.Expression + ) + ); + } + + /// + /// Synchronizes the observable sequence such that observer notifications cannot be delivered concurrently. + /// This overload is useful to "fix" an observable sequence that exhibits concurrent callbacks on individual observers, which is invalid behavior for the query processor. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// The source sequence whose outgoing calls to observers are synchronized. + /// + /// is null. + /// + /// It's invalid behavior - according to the observer grammar - for a sequence to exhibit concurrent callbacks on a given observer. + /// This operator can be used to "fix" a source that doesn't conform to this rule. + /// + public static IQbservable Synchronize(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Synchronizes the observable sequence such that observer notifications cannot be delivered concurrently, using the specified gate object. + /// This overload is useful when writing n-ary query operators, in order to prevent concurrent callbacks from different sources by synchronizing on a common gate object. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Gate object to synchronize each observer call on. + /// The source sequence whose outgoing calls to observers are synchronized on the given gate object. + /// + /// or is null. + public static IQbservable Synchronize(this IQbservable source, object gate) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (gate == null) + throw new ArgumentNullException(nameof(gate)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(gate, typeof(object)) + ) + ); + } + + /// + /// Returns a specified number of contiguous elements from the start of an observable sequence. + /// + /// The type of the elements in the source sequence. + /// The sequence to take elements from. + /// The number of elements to return. + /// An observable sequence that contains the specified number of elements from the start of the input sequence. + /// + /// is null. + /// + /// is less than zero. + public static IQbservable Take(this IQbservable source, int count) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(count, typeof(int)) + ) + ); + } + + /// + /// Returns a specified number of contiguous elements from the start of an observable sequence, using the specified scheduler for the edge case of Take(0). + /// + /// The type of the elements in the source sequence. + /// The sequence to take elements from. + /// The number of elements to return. + /// Scheduler used to produce an OnCompleted message in case count is set to 0. + /// An observable sequence that contains the specified number of elements from the start of the input sequence. + /// + /// or is null. + /// + /// is less than zero. + public static IQbservable Take(this IQbservable source, int count, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(count, typeof(int)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Takes elements for the specified duration from the start of the observable source sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to take elements from. + /// Duration for taking elements from the start of the sequence. + /// An observable sequence with the elements taken during the specified duration from the start of the source sequence. + /// + /// is null. + /// + /// is less than TimeSpan.Zero. + /// + /// Specifying a TimeSpan.Zero value for doesn't guarantee an empty sequence will be returned. This is a side-effect + /// of the asynchrony introduced by the scheduler, where the action that stops forwarding callbacks from the source sequence may not execute + /// immediately, despite the TimeSpan.Zero due time. + /// + public static IQbservable Take(this IQbservable source, TimeSpan duration) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(duration, typeof(TimeSpan)) + ) + ); + } + + /// + /// Takes elements for the specified duration from the start of the observable source sequence, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence. + /// Source sequence to take elements from. + /// Duration for taking elements from the start of the sequence. + /// Scheduler to run the timer on. + /// An observable sequence with the elements taken during the specified duration from the start of the source sequence. + /// + /// or is null. + /// + /// is less than TimeSpan.Zero. + /// + /// Specifying a TimeSpan.Zero value for doesn't guarantee an empty sequence will be returned. This is a side-effect + /// of the asynchrony introduced by the scheduler, where the action that stops forwarding callbacks from the source sequence may not execute + /// immediately, despite the TimeSpan.Zero due time. + /// + public static IQbservable Take(this IQbservable source, TimeSpan duration, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(duration, typeof(TimeSpan)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Returns a specified number of contiguous elements from the end of an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Number of elements to take from the end of the source sequence. + /// An observable sequence containing the specified number of elements from the end of the source sequence. + /// + /// is null. + /// + /// is less than zero. + /// + /// This operator accumulates a buffer with a length enough to store elements elements. Upon completion of + /// the source sequence, this buffer is drained on the result sequence. This causes the elements to be delayed. + /// + public static IQbservable TakeLast(this IQbservable source, int count) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(count, typeof(int)) + ) + ); + } + + /// + /// Returns a specified number of contiguous elements from the end of an observable sequence, using the specified scheduler to drain the queue. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Number of elements to take from the end of the source sequence. + /// Scheduler used to drain the queue upon completion of the source sequence. + /// An observable sequence containing the specified number of elements from the end of the source sequence. + /// + /// or is null. + /// + /// is less than zero. + /// + /// This operator accumulates a buffer with a length enough to store elements elements. Upon completion of + /// the source sequence, this buffer is drained on the result sequence. This causes the elements to be delayed. + /// + public static IQbservable TakeLast(this IQbservable source, int count, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(count, typeof(int)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Returns elements within the specified duration from the end of the observable source sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to take elements from. + /// Duration for taking elements from the end of the sequence. + /// An observable sequence with the elements taken during the specified duration from the end of the source sequence. + /// + /// is null. + /// + /// is less than TimeSpan.Zero. + /// + /// This operator accumulates a buffer with a length enough to store elements for any window during the lifetime of + /// the source sequence. Upon completion of the source sequence, this buffer is drained on the result sequence. This causes the result elements + /// to be delayed with . + /// + public static IQbservable TakeLast(this IQbservable source, TimeSpan duration) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(duration, typeof(TimeSpan)) + ) + ); + } + + /// + /// Returns elements within the specified duration from the end of the observable source sequence, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence. + /// Source sequence to take elements from. + /// Duration for taking elements from the end of the sequence. + /// Scheduler to run the timer on. + /// An observable sequence with the elements taken during the specified duration from the end of the source sequence. + /// + /// or is null. + /// + /// is less than TimeSpan.Zero. + /// + /// This operator accumulates a buffer with a length enough to store elements for any window during the lifetime of + /// the source sequence. Upon completion of the source sequence, this buffer is drained on the result sequence. This causes the result elements + /// to be delayed with . + /// + public static IQbservable TakeLast(this IQbservable source, TimeSpan duration, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(duration, typeof(TimeSpan)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Returns elements within the specified duration from the end of the observable source sequence, using the specified schedulers to run timers and to drain the collected elements. + /// + /// The type of the elements in the source sequence. + /// Source sequence to take elements from. + /// Duration for taking elements from the end of the sequence. + /// Scheduler to run the timer on. + /// Scheduler to drain the collected elements. + /// An observable sequence with the elements taken during the specified duration from the end of the source sequence. + /// + /// or or is null. + /// + /// is less than TimeSpan.Zero. + /// + /// This operator accumulates a buffer with a length enough to store elements for any window during the lifetime of + /// the source sequence. Upon completion of the source sequence, this buffer is drained on the result sequence. This causes the result elements + /// to be delayed with . + /// + public static IQbservable TakeLast(this IQbservable source, TimeSpan duration, IScheduler timerScheduler, IScheduler loopScheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (timerScheduler == null) + throw new ArgumentNullException(nameof(timerScheduler)); + if (loopScheduler == null) + throw new ArgumentNullException(nameof(loopScheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(duration, typeof(TimeSpan)), + Expression.Constant(timerScheduler, typeof(IScheduler)), + Expression.Constant(loopScheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Returns a list with the specified number of contiguous elements from the end of an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence. + /// Number of elements to take from the end of the source sequence. + /// An observable sequence containing a single list with the specified number of elements from the end of the source sequence. + /// + /// is null. + /// + /// is less than zero. + /// + /// This operator accumulates a buffer with a length enough to store elements. Upon completion of the + /// source sequence, this buffer is produced on the result sequence. + /// + public static IQbservable> TakeLastBuffer(this IQbservable source, int count) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(count, typeof(int)) + ) + ); + } + + /// + /// Returns a list with the elements within the specified duration from the end of the observable source sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to take elements from. + /// Duration for taking elements from the end of the sequence. + /// An observable sequence containing a single list with the elements taken during the specified duration from the end of the source sequence. + /// + /// is null. + /// + /// is less than TimeSpan.Zero. + /// + /// This operator accumulates a buffer with a length enough to store elements for any window during the lifetime of + /// the source sequence. Upon completion of the source sequence, this buffer is produced on the result sequence. + /// + public static IQbservable> TakeLastBuffer(this IQbservable source, TimeSpan duration) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(duration, typeof(TimeSpan)) + ) + ); + } + + /// + /// Returns a list with the elements within the specified duration from the end of the observable source sequence, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence. + /// Source sequence to take elements from. + /// Duration for taking elements from the end of the sequence. + /// Scheduler to run the timer on. + /// An observable sequence containing a single list with the elements taken during the specified duration from the end of the source sequence. + /// + /// or is null. + /// + /// is less than TimeSpan.Zero. + /// + /// This operator accumulates a buffer with a length enough to store elements for any window during the lifetime of + /// the source sequence. Upon completion of the source sequence, this buffer is produced on the result sequence. + /// + public static IQbservable> TakeLastBuffer(this IQbservable source, TimeSpan duration, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(duration, typeof(TimeSpan)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Takes elements for the specified duration until the specified end time. + /// + /// The type of the elements in the source sequence. + /// Source sequence to take elements from. + /// Time to stop taking elements from the source sequence. If this value is less than or equal to DateTimeOffset.UtcNow, the result stream will complete immediately. + /// An observable sequence with the elements taken until the specified end time. + /// + /// is null. + public static IQbservable TakeUntil(this IQbservable source, DateTimeOffset endTime) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(endTime, typeof(DateTimeOffset)) + ) + ); + } + + /// + /// Takes elements for the specified duration until the specified end time, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence. + /// Source sequence to take elements from. + /// Time to stop taking elements from the source sequence. If this value is less than or equal to DateTimeOffset.UtcNow, the result stream will complete immediately. + /// Scheduler to run the timer on. + /// An observable sequence with the elements taken until the specified end time. + /// + /// or is null. + public static IQbservable TakeUntil(this IQbservable source, DateTimeOffset endTime, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(endTime, typeof(DateTimeOffset)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Returns the elements from the source observable sequence until the other observable sequence produces an element. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the other sequence that indicates the end of take behavior. + /// Source sequence to propagate elements for. + /// Observable sequence that terminates propagation of elements of the source sequence. + /// An observable sequence containing the elements of the source sequence up to the point the other sequence interrupted further propagation. + /// + /// or is null. + public static IQbservable TakeUntil(this IQbservable source, IObservable other) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (other == null) + throw new ArgumentNullException(nameof(other)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TOther)), + source.Expression, + GetSourceExpression(other) + ) + ); + } + + /// + /// Relays elements from the source observable sequence and calls the predicate after an + /// emission to check if the sequence should stop after that specific item. + /// + /// The type of the elements in the source and result sequences. + /// The source sequence to relay elements of. + /// Called after each upstream item has been emitted with + /// that upstream item and should return true to indicate the sequence should + /// complete. + /// The observable sequence with the source elements until the stop predicate returns true. + /// + /// The following sequence will stop after the value 5 has been encountered: + /// + /// Observable.Range(1, 10) + /// .TakeUntil(item => item == 5) + /// .Subscribe(Console.WriteLine); + /// + /// + /// If or is null. + public static IQbservable TakeUntil(this IQbservable source, Expression> stopPredicate) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (stopPredicate == null) + throw new ArgumentNullException(nameof(stopPredicate)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + stopPredicate + ) + ); + } + + /// + /// Returns elements from an observable sequence as long as a specified condition is true. + /// + /// The type of the elements in the source sequence. + /// A sequence to return elements from. + /// A function to test each element for a condition. + /// An observable sequence that contains the elements from the input sequence that occur before the element at which the test no longer passes. + /// + /// or is null. + public static IQbservable TakeWhile(this IQbservable source, Expression> predicate) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (predicate == null) + throw new ArgumentNullException(nameof(predicate)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + predicate + ) + ); + } + + /// + /// Returns elements from an observable sequence as long as a specified condition is true. + /// The element's index is used in the logic of the predicate function. + /// + /// The type of the elements in the source sequence. + /// A sequence to return elements from. + /// A function to test each element for a condition; the second parameter of the function represents the index of the source element. + /// An observable sequence that contains the elements from the input sequence that occur before the element at which the test no longer passes. + /// + /// or is null. + public static IQbservable TakeWhile(this IQbservable source, Expression> predicate) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (predicate == null) + throw new ArgumentNullException(nameof(predicate)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + predicate + ) + ); + } + + /// + /// Ignores elements from an observable sequence which are followed by another element within a specified relative time duration. + /// + /// The type of the elements in the source sequence. + /// Source sequence to throttle. + /// Throttling duration for each element. + /// The throttled sequence. + /// + /// is null. + /// + /// is less than TimeSpan.Zero. + /// + /// + /// This operator throttles the source sequence by holding on to each element for the duration specified in . If another + /// element is produced within this time window, the element is dropped and a new timer is started for the current element, repeating this whole + /// process. For streams that never have gaps larger than or equal to between elements, the resulting stream won't + /// produce any elements. In order to reduce the volume of a stream whilst guaranteeing the periodic production of elements, consider using the + /// Observable.Sample set of operators. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing throttling timers to be scheduled + /// that are due immediately. However, this doesn't guarantee all elements will be retained in the result sequence. This is a side-effect of the + /// asynchrony introduced by the scheduler, where the action to forward the current element may not execute immediately, despite the TimeSpan.Zero + /// due time. In such cases, the next element may arrive before the scheduler gets a chance to run the throttling action. + /// + /// + public static IQbservable Throttle(this IQbservable source, TimeSpan dueTime) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(dueTime, typeof(TimeSpan)) + ) + ); + } + + /// + /// Ignores elements from an observable sequence which are followed by another element within a specified relative time duration, using the specified scheduler to run throttling timers. + /// + /// The type of the elements in the source sequence. + /// Source sequence to throttle. + /// Throttling duration for each element. + /// Scheduler to run the throttle timers on. + /// The throttled sequence. + /// + /// or is null. + /// + /// is less than TimeSpan.Zero. + /// + /// + /// This operator throttles the source sequence by holding on to each element for the duration specified in . If another + /// element is produced within this time window, the element is dropped and a new timer is started for the current element, repeating this whole + /// process. For streams that never have gaps larger than or equal to between elements, the resulting stream won't + /// produce any elements. In order to reduce the volume of a stream whilst guaranteeing the periodic production of elements, consider using the + /// Observable.Sample set of operators. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing throttling timers to be scheduled + /// that are due immediately. However, this doesn't guarantee all elements will be retained in the result sequence. This is a side-effect of the + /// asynchrony introduced by the scheduler, where the action to forward the current element may not execute immediately, despite the TimeSpan.Zero + /// due time. In such cases, the next element may arrive before the scheduler gets a chance to run the throttling action. + /// + /// + public static IQbservable Throttle(this IQbservable source, TimeSpan dueTime, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(dueTime, typeof(TimeSpan)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Ignores elements from an observable sequence which are followed by another value within a computed throttle duration. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the throttle sequences selected for each element in the source sequence. + /// Source sequence to throttle. + /// Selector function to retrieve a sequence indicating the throttle duration for each given element. + /// The throttled sequence. + /// + /// or is null. + /// + /// This operator throttles the source sequence by holding on to each element for the duration denoted by . + /// If another element is produced within this time window, the element is dropped and a new timer is started for the current element, repeating this + /// whole process. For streams where the duration computed by applying the to each element overlaps with + /// the occurrence of the successor element, the resulting stream won't produce any elements. In order to reduce the volume of a stream whilst + /// guaranteeing the periodic production of elements, consider using the Observable.Sample set of operators. + /// + public static IQbservable Throttle(this IQbservable source, Expression>> throttleDurationSelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (throttleDurationSelector == null) + throw new ArgumentNullException(nameof(throttleDurationSelector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TThrottle)), + source.Expression, + throttleDurationSelector + ) + ); + } + + /// + /// Returns an observable sequence that terminates with an exception. + /// + /// Query provider used to construct the data source. + /// The type used for the type parameter of the resulting sequence. + /// Exception object used for the sequence's termination. + /// The observable sequence that terminates exceptionally with the specified exception object. + /// + /// is null. + public static IQbservable Throw(this IQbservableProvider provider, Exception exception) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (exception == null) + throw new ArgumentNullException(nameof(exception)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(exception, typeof(Exception)) + ) + ); + } + + /// + /// Returns an observable sequence that terminates with an exception, using the specified scheduler to send out the single OnError message. + /// + /// Query provider used to construct the data source. + /// The type used for the type parameter of the resulting sequence. + /// Exception object used for the sequence's termination. + /// Scheduler to send the exceptional termination call on. + /// The observable sequence that terminates exceptionally with the specified exception object. + /// + /// or is null. + public static IQbservable Throw(this IQbservableProvider provider, Exception exception, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (exception == null) + throw new ArgumentNullException(nameof(exception)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(exception, typeof(Exception)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Returns an observable sequence that terminates with an exception, using the specified scheduler to send out the single OnError message. + /// + /// Query provider used to construct the data source. + /// The type used for the type parameter of the resulting sequence. + /// Exception object used for the sequence's termination. + /// Scheduler to send the exceptional termination call on. + /// Object solely used to infer the type of the type parameter. This parameter is typically used when creating a sequence of anonymously typed elements. + /// The observable sequence that terminates exceptionally with the specified exception object. + /// + /// or is null. + public static IQbservable Throw(this IQbservableProvider provider, Exception exception, IScheduler scheduler, TResult witness) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (exception == null) + throw new ArgumentNullException(nameof(exception)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(exception, typeof(Exception)), + Expression.Constant(scheduler, typeof(IScheduler)), + Expression.Constant(witness, typeof(TResult)) + ) + ); + } + + /// + /// Returns an observable sequence that terminates with an exception. + /// + /// Query provider used to construct the data source. + /// The type used for the type parameter of the resulting sequence. + /// Exception object used for the sequence's termination. + /// Object solely used to infer the type of the type parameter. This parameter is typically used when creating a sequence of anonymously typed elements. + /// The observable sequence that terminates exceptionally with the specified exception object. + /// + /// is null. + public static IQbservable Throw(this IQbservableProvider provider, Exception exception, TResult witness) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (exception == null) + throw new ArgumentNullException(nameof(exception)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(exception, typeof(Exception)), + Expression.Constant(witness, typeof(TResult)) + ) + ); + } + + /// + /// Records the time interval between consecutive elements in an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Source sequence to record time intervals for. + /// An observable sequence with time interval information on elements. + /// + /// is null. + public static IQbservable> TimeInterval(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Records the time interval between consecutive elements in an observable sequence, using the specified scheduler to compute time intervals. + /// + /// The type of the elements in the source sequence. + /// Source sequence to record time intervals for. + /// Scheduler used to compute time intervals. + /// An observable sequence with time interval information on elements. + /// + /// or is null. + public static IQbservable> TimeInterval(this IQbservable source, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Applies a timeout policy to the observable sequence based on an absolute time. + /// If the sequence doesn't terminate before the specified absolute due time, a TimeoutException is propagated to the observer. + /// + /// The type of the elements in the source sequence. + /// Source sequence to perform a timeout for. + /// Time when a timeout occurs. If this value is less than or equal to DateTimeOffset.UtcNow, the timeout occurs immediately. + /// The source sequence with a TimeoutException in case of a timeout. + /// + /// is null. + /// (Asynchronous) If the sequence hasn't terminated before . + /// + /// In case you only want to timeout on the first element, consider using the + /// operator applied to the source sequence and a delayed sequence. Alternatively, the general-purpose overload + /// of Timeout, can be used. + /// + public static IQbservable Timeout(this IQbservable source, DateTimeOffset dueTime) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(dueTime, typeof(DateTimeOffset)) + ) + ); + } + + /// + /// Applies a timeout policy to the observable sequence based on an absolute time. + /// If the sequence doesn't terminate before the specified absolute due time, the other observable sequence is used to produce future messages from that point on. + /// + /// The type of the elements in the source sequence and the other sequence used upon a timeout. + /// Source sequence to perform a timeout for. + /// Time when a timeout occurs. If this value is less than or equal to DateTimeOffset.UtcNow, the timeout occurs immediately. + /// Sequence to return in case of a timeout. + /// The source sequence switching to the other sequence in case of a timeout. + /// + /// or is null. + /// + /// In case you only want to timeout on the first element, consider using the + /// operator applied to the source sequence and a delayed sequence. Alternatively, the general-purpose overload + /// of Timeout, can be used. + /// + public static IQbservable Timeout(this IQbservable source, DateTimeOffset dueTime, IObservable other) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (other == null) + throw new ArgumentNullException(nameof(other)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(dueTime, typeof(DateTimeOffset)), + GetSourceExpression(other) + ) + ); + } + + /// + /// Applies a timeout policy to the observable sequence based on an absolute time, using the specified scheduler to run timeout timers. + /// If the sequence doesn't terminate before the specified absolute due time, the other observable sequence is used to produce future messages from that point on. + /// + /// The type of the elements in the source sequence and the other sequence used upon a timeout. + /// Source sequence to perform a timeout for. + /// Time when a timeout occurs. If this value is less than or equal to DateTimeOffset.UtcNow, the timeout occurs immediately. + /// Sequence to return in case of a timeout. + /// Scheduler to run the timeout timers on. + /// The source sequence switching to the other sequence in case of a timeout. + /// + /// or or is null. + /// + /// In case you only want to timeout on the first element, consider using the + /// operator applied to the source sequence and a delayed sequence. Alternatively, the general-purpose overload + /// of Timeout, can be used. + /// + public static IQbservable Timeout(this IQbservable source, DateTimeOffset dueTime, IObservable other, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (other == null) + throw new ArgumentNullException(nameof(other)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(dueTime, typeof(DateTimeOffset)), + GetSourceExpression(other), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Applies a timeout policy to the observable sequence based on an absolute time, using the specified scheduler to run timeout timers. + /// If the sequence doesn't terminate before the specified absolute due time, a TimeoutException is propagated to the observer. + /// + /// The type of the elements in the source sequence. + /// Source sequence to perform a timeout for. + /// Time when a timeout occurs. If this value is less than or equal to DateTimeOffset.UtcNow, the timeout occurs immediately. + /// Scheduler to run the timeout timers on. + /// The source sequence with a TimeoutException in case of a timeout. + /// + /// or is null. + /// (Asynchronous) If the sequence hasn't terminated before . + /// + /// In case you only want to timeout on the first element, consider using the + /// operator applied to the source sequence and a delayed sequence. Alternatively, the general-purpose overload + /// of Timeout, can be used. + /// + public static IQbservable Timeout(this IQbservable source, DateTimeOffset dueTime, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(dueTime, typeof(DateTimeOffset)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Applies a timeout policy for each element in the observable sequence. + /// If the next element isn't received within the specified timeout duration starting from its predecessor, a TimeoutException is propagated to the observer. + /// + /// The type of the elements in the source sequence. + /// Source sequence to perform a timeout for. + /// Maximum duration between values before a timeout occurs. + /// The source sequence with a TimeoutException in case of a timeout. + /// + /// is null. + /// + /// is less than TimeSpan.Zero. + /// (Asynchronous) If no element is produced within from the previous element. + /// + /// + /// In case you only want to timeout on the first element, consider using the + /// operator applied to the source sequence and a delayed sequence. Alternatively, the general-purpose overload + /// of Timeout, can be used. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing timeout timers to be scheduled that are due + /// immediately. However, this doesn't guarantee a timeout will occur, even for the first element. This is a side-effect of the asynchrony introduced by the + /// scheduler, where the action to propagate a timeout may not execute immediately, despite the TimeSpan.Zero due time. In such cases, the next element may + /// arrive before the scheduler gets a chance to run the timeout action. + /// + /// + public static IQbservable Timeout(this IQbservable source, TimeSpan dueTime) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(dueTime, typeof(TimeSpan)) + ) + ); + } + + /// + /// Applies a timeout policy for each element in the observable sequence. + /// If the next element isn't received within the specified timeout duration starting from its predecessor, the other observable sequence is used to produce future messages from that point on. + /// + /// The type of the elements in the source sequence and the other sequence used upon a timeout. + /// Source sequence to perform a timeout for. + /// Maximum duration between values before a timeout occurs. + /// Sequence to return in case of a timeout. + /// The source sequence switching to the other sequence in case of a timeout. + /// + /// or is null. + /// + /// is less than TimeSpan.Zero. + /// + /// + /// In case you only want to timeout on the first element, consider using the + /// operator applied to the source sequence and a delayed sequence. Alternatively, the general-purpose overload + /// of Timeout, can be used. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing timeout timers to be scheduled that are due + /// immediately. However, this doesn't guarantee a timeout will occur, even for the first element. This is a side-effect of the asynchrony introduced by the + /// scheduler, where the action to propagate a timeout may not execute immediately, despite the TimeSpan.Zero due time. In such cases, the next element may + /// arrive before the scheduler gets a chance to run the timeout action. + /// + /// + public static IQbservable Timeout(this IQbservable source, TimeSpan dueTime, IObservable other) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (other == null) + throw new ArgumentNullException(nameof(other)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(dueTime, typeof(TimeSpan)), + GetSourceExpression(other) + ) + ); + } + + /// + /// Applies a timeout policy for each element in the observable sequence, using the specified scheduler to run timeout timers. + /// If the next element isn't received within the specified timeout duration starting from its predecessor, the other observable sequence is used to produce future messages from that point on. + /// + /// The type of the elements in the source sequence and the other sequence used upon a timeout. + /// Source sequence to perform a timeout for. + /// Maximum duration between values before a timeout occurs. + /// Sequence to return in case of a timeout. + /// Scheduler to run the timeout timers on. + /// The source sequence switching to the other sequence in case of a timeout. + /// + /// or or is null. + /// + /// is less than TimeSpan.Zero. + /// + /// + /// In case you only want to timeout on the first element, consider using the + /// operator applied to the source sequence and a delayed sequence. Alternatively, the general-purpose overload + /// of Timeout, can be used. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing timeout timers to be scheduled that are due + /// immediately. However, this doesn't guarantee a timeout will occur, even for the first element. This is a side-effect of the asynchrony introduced by the + /// scheduler, where the action to propagate a timeout may not execute immediately, despite the TimeSpan.Zero due time. In such cases, the next element may + /// arrive before the scheduler gets a chance to run the timeout action. + /// + /// + public static IQbservable Timeout(this IQbservable source, TimeSpan dueTime, IObservable other, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (other == null) + throw new ArgumentNullException(nameof(other)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(dueTime, typeof(TimeSpan)), + GetSourceExpression(other), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Applies a timeout policy for each element in the observable sequence, using the specified scheduler to run timeout timers. + /// If the next element isn't received within the specified timeout duration starting from its predecessor, a TimeoutException is propagated to the observer. + /// + /// The type of the elements in the source sequence. + /// Source sequence to perform a timeout for. + /// Maximum duration between values before a timeout occurs. + /// Scheduler to run the timeout timers on. + /// The source sequence with a TimeoutException in case of a timeout. + /// + /// or is null. + /// + /// is less than TimeSpan.Zero. + /// (Asynchronous) If no element is produced within from the previous element. + /// + /// + /// In case you only want to timeout on the first element, consider using the + /// operator applied to the source sequence and a delayed sequence. Alternatively, the general-purpose overload + /// of Timeout, can be used. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing timeout timers to be scheduled that are due + /// immediately. However, this doesn't guarantee a timeout will occur, even for the first element. This is a side-effect of the asynchrony introduced by the + /// scheduler, where the action to propagate a timeout may not execute immediately, despite the TimeSpan.Zero due time. In such cases, the next element may + /// arrive before the scheduler gets a chance to run the timeout action. + /// + /// + public static IQbservable Timeout(this IQbservable source, TimeSpan dueTime, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(dueTime, typeof(TimeSpan)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Applies a timeout policy to the observable sequence based on an initial timeout duration for the first element, and a timeout duration computed for each subsequent element. + /// If the next element isn't received within the computed duration starting from its predecessor, a TimeoutException is propagated to the observer. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the timeout sequences used to indicate the timeout duration for each element in the source sequence. + /// Source sequence to perform a timeout for. + /// Observable sequence that represents the timeout for the first element. + /// Selector to retrieve an observable sequence that represents the timeout between the current element and the next element. + /// The source sequence with a TimeoutException in case of a timeout. + /// + /// or or is null. + public static IQbservable Timeout(this IQbservable source, IObservable firstTimeout, Expression>> timeoutDurationSelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (firstTimeout == null) + throw new ArgumentNullException(nameof(firstTimeout)); + if (timeoutDurationSelector == null) + throw new ArgumentNullException(nameof(timeoutDurationSelector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TTimeout)), + source.Expression, + GetSourceExpression(firstTimeout), + timeoutDurationSelector + ) + ); + } + + /// + /// Applies a timeout policy to the observable sequence based on an initial timeout duration for the first element, and a timeout duration computed for each subsequent element. + /// If the next element isn't received within the computed duration starting from its predecessor, the other observable sequence is used to produce future messages from that point on. + /// + /// The type of the elements in the source sequence and the other sequence used upon a timeout. + /// The type of the elements in the timeout sequences used to indicate the timeout duration for each element in the source sequence. + /// Source sequence to perform a timeout for. + /// Observable sequence that represents the timeout for the first element. + /// Selector to retrieve an observable sequence that represents the timeout between the current element and the next element. + /// Sequence to return in case of a timeout. + /// The source sequence switching to the other sequence in case of a timeout. + /// + /// or or or is null. + public static IQbservable Timeout(this IQbservable source, IObservable firstTimeout, Expression>> timeoutDurationSelector, IObservable other) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (firstTimeout == null) + throw new ArgumentNullException(nameof(firstTimeout)); + if (timeoutDurationSelector == null) + throw new ArgumentNullException(nameof(timeoutDurationSelector)); + if (other == null) + throw new ArgumentNullException(nameof(other)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TTimeout)), + source.Expression, + GetSourceExpression(firstTimeout), + timeoutDurationSelector, + GetSourceExpression(other) + ) + ); + } + + /// + /// Applies a timeout policy to the observable sequence based on a timeout duration computed for each element. + /// If the next element isn't received within the computed duration starting from its predecessor, a TimeoutException is propagated to the observer. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the timeout sequences used to indicate the timeout duration for each element in the source sequence. + /// Source sequence to perform a timeout for. + /// Selector to retrieve an observable sequence that represents the timeout between the current element and the next element. + /// The source sequence with a TimeoutException in case of a timeout. + /// + /// or is null. + public static IQbservable Timeout(this IQbservable source, Expression>> timeoutDurationSelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (timeoutDurationSelector == null) + throw new ArgumentNullException(nameof(timeoutDurationSelector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TTimeout)), + source.Expression, + timeoutDurationSelector + ) + ); + } + + /// + /// Applies a timeout policy to the observable sequence based on a timeout duration computed for each element. + /// If the next element isn't received within the computed duration starting from its predecessor, the other observable sequence is used to produce future messages from that point on. + /// + /// The type of the elements in the source sequence and the other sequence used upon a timeout. + /// The type of the elements in the timeout sequences used to indicate the timeout duration for each element in the source sequence. + /// Source sequence to perform a timeout for. + /// Selector to retrieve an observable sequence that represents the timeout between the current element and the next element. + /// Sequence to return in case of a timeout. + /// The source sequence switching to the other sequence in case of a timeout. + /// + /// or or is null. + public static IQbservable Timeout(this IQbservable source, Expression>> timeoutDurationSelector, IObservable other) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (timeoutDurationSelector == null) + throw new ArgumentNullException(nameof(timeoutDurationSelector)); + if (other == null) + throw new ArgumentNullException(nameof(other)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TTimeout)), + source.Expression, + timeoutDurationSelector, + GetSourceExpression(other) + ) + ); + } + + /// + /// Returns an observable sequence that produces a single value at the specified absolute due time. + /// + /// Query provider used to construct the data source. + /// Absolute time at which to produce the value. If this value is less than or equal to DateTimeOffset.UtcNow, the timer will fire as soon as possible. + /// An observable sequence that produces a value at due time. + public static IQbservable Timer(this IQbservableProvider provider, DateTimeOffset dueTime) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(dueTime, typeof(DateTimeOffset)) + ) + ); + } + + /// + /// Returns an observable sequence that periodically produces a value starting at the specified initial absolute due time. + /// + /// Query provider used to construct the data source. + /// Absolute time at which to produce the first value. If this value is less than or equal to DateTimeOffset.UtcNow, the timer will fire as soon as possible. + /// Period to produce subsequent values. If this value is equal to TimeSpan.Zero, the timer will recur as fast as possible. + /// An observable sequence that produces a value at due time and then after each period. + /// + /// is less than TimeSpan.Zero. + public static IQbservable Timer(this IQbservableProvider provider, DateTimeOffset dueTime, TimeSpan period) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(dueTime, typeof(DateTimeOffset)), + Expression.Constant(period, typeof(TimeSpan)) + ) + ); + } + + /// + /// Returns an observable sequence that periodically produces a value starting at the specified initial absolute due time, using the specified scheduler to run timers. + /// + /// Query provider used to construct the data source. + /// Absolute time at which to produce the first value. If this value is less than or equal to DateTimeOffset.UtcNow, the timer will fire as soon as possible. + /// Period to produce subsequent values. If this value is equal to TimeSpan.Zero, the timer will recur as fast as possible. + /// Scheduler to run timers on. + /// An observable sequence that produces a value at due time and then after each period. + /// + /// is less than TimeSpan.Zero. + /// + /// is null. + public static IQbservable Timer(this IQbservableProvider provider, DateTimeOffset dueTime, TimeSpan period, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(dueTime, typeof(DateTimeOffset)), + Expression.Constant(period, typeof(TimeSpan)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Returns an observable sequence that produces a single value at the specified absolute due time, using the specified scheduler to run the timer. + /// + /// Query provider used to construct the data source. + /// Absolute time at which to produce the value. If this value is less than or equal to DateTimeOffset.UtcNow, the timer will fire as soon as possible. + /// Scheduler to run the timer on. + /// An observable sequence that produces a value at due time. + /// + /// is null. + public static IQbservable Timer(this IQbservableProvider provider, DateTimeOffset dueTime, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(dueTime, typeof(DateTimeOffset)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Returns an observable sequence that produces a single value after the specified relative due time has elapsed. + /// + /// Query provider used to construct the data source. + /// Relative time at which to produce the value. If this value is less than or equal to TimeSpan.Zero, the timer will fire as soon as possible. + /// An observable sequence that produces a value after the due time has elapsed. + public static IQbservable Timer(this IQbservableProvider provider, TimeSpan dueTime) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(dueTime, typeof(TimeSpan)) + ) + ); + } + + /// + /// Returns an observable sequence that periodically produces a value after the specified initial relative due time has elapsed. + /// + /// Query provider used to construct the data source. + /// Relative time at which to produce the first value. If this value is less than or equal to TimeSpan.Zero, the timer will fire as soon as possible. + /// Period to produce subsequent values. If this value is equal to TimeSpan.Zero, the timer will recur as fast as possible. + /// An observable sequence that produces a value after due time has elapsed and then after each period. + /// + /// is less than TimeSpan.Zero. + public static IQbservable Timer(this IQbservableProvider provider, TimeSpan dueTime, TimeSpan period) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(dueTime, typeof(TimeSpan)), + Expression.Constant(period, typeof(TimeSpan)) + ) + ); + } + + /// + /// Returns an observable sequence that periodically produces a value after the specified initial relative due time has elapsed, using the specified scheduler to run timers. + /// + /// Query provider used to construct the data source. + /// Relative time at which to produce the first value. If this value is less than or equal to TimeSpan.Zero, the timer will fire as soon as possible. + /// Period to produce subsequent values. If this value is equal to TimeSpan.Zero, the timer will recur as fast as possible. + /// Scheduler to run timers on. + /// An observable sequence that produces a value after due time has elapsed and then each period. + /// + /// is less than TimeSpan.Zero. + /// + /// is null. + public static IQbservable Timer(this IQbservableProvider provider, TimeSpan dueTime, TimeSpan period, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(dueTime, typeof(TimeSpan)), + Expression.Constant(period, typeof(TimeSpan)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Returns an observable sequence that produces a single value after the specified relative due time has elapsed, using the specified scheduler to run the timer. + /// + /// Query provider used to construct the data source. + /// Relative time at which to produce the value. If this value is less than or equal to TimeSpan.Zero, the timer will fire as soon as possible. + /// Scheduler to run the timer on. + /// An observable sequence that produces a value after the due time has elapsed. + /// + /// is null. + public static IQbservable Timer(this IQbservableProvider provider, TimeSpan dueTime, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod()!, + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(dueTime, typeof(TimeSpan)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Timestamps each element in an observable sequence using the local system clock. + /// + /// The type of the elements in the source sequence. + /// Source sequence to timestamp elements for. + /// An observable sequence with timestamp information on elements. + /// + /// is null. + public static IQbservable> Timestamp(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Timestamp each element in an observable sequence using the clock of the specified scheduler. + /// + /// The type of the elements in the source sequence. + /// Source sequence to timestamp elements for. + /// Scheduler used to compute timestamps. + /// An observable sequence with timestamp information on elements. + /// + /// or is null. + public static IQbservable> Timestamp(this IQbservable source, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Creates an array from an observable sequence. + /// + /// The type of the elements in the source sequence. + /// The source observable sequence to get an array of elements for. + /// An observable sequence containing a single element with an array containing all the elements of the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable ToArray(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Creates a dictionary from an observable sequence according to a specified key selector function. + /// + /// The type of the elements in the source sequence. + /// The type of the dictionary key computed for each element in the source sequence. + /// An observable sequence to create a dictionary for. + /// A function to extract a key from each element. + /// An observable sequence containing a single element with a dictionary mapping unique key values onto the corresponding source sequence's element. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable> ToDictionary(this IQbservable source, Expression> keySelector) + where TKey : notnull + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey)), + source.Expression, + keySelector + ) + ); + } + + /// + /// Creates a dictionary from an observable sequence according to a specified key selector function, and a comparer. + /// + /// The type of the elements in the source sequence. + /// The type of the dictionary key computed for each element in the source sequence. + /// An observable sequence to create a dictionary for. + /// A function to extract a key from each element. + /// An equality comparer to compare keys. + /// An observable sequence containing a single element with a dictionary mapping unique key values onto the corresponding source sequence's element. + /// + /// or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable> ToDictionary(this IQbservable source, Expression> keySelector, IEqualityComparer comparer) + where TKey : notnull + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey)), + source.Expression, + keySelector, + Expression.Constant(comparer, typeof(IEqualityComparer)) + ) + ); + } + + /// + /// Creates a dictionary from an observable sequence according to a specified key selector function, and an element selector function. + /// + /// The type of the elements in the source sequence. + /// The type of the dictionary key computed for each element in the source sequence. + /// The type of the dictionary value computed for each element in the source sequence. + /// An observable sequence to create a dictionary for. + /// A function to extract a key from each element. + /// A transform function to produce a result element value from each element. + /// An observable sequence containing a single element with a dictionary mapping unique key values onto the corresponding source sequence's element. + /// + /// or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable> ToDictionary(this IQbservable source, Expression> keySelector, Expression> elementSelector) + where TKey : notnull + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (elementSelector == null) + throw new ArgumentNullException(nameof(elementSelector)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey), typeof(TElement)), + source.Expression, + keySelector, + elementSelector + ) + ); + } + + /// + /// Creates a dictionary from an observable sequence according to a specified key selector function, a comparer, and an element selector function. + /// + /// The type of the elements in the source sequence. + /// The type of the dictionary key computed for each element in the source sequence. + /// The type of the dictionary value computed for each element in the source sequence. + /// An observable sequence to create a dictionary for. + /// A function to extract a key from each element. + /// A transform function to produce a result element value from each element. + /// An equality comparer to compare keys. + /// An observable sequence containing a single element with a dictionary mapping unique key values onto the corresponding source sequence's element. + /// + /// or or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable> ToDictionary(this IQbservable source, Expression> keySelector, Expression> elementSelector, IEqualityComparer comparer) + where TKey : notnull + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (elementSelector == null) + throw new ArgumentNullException(nameof(elementSelector)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey), typeof(TElement)), + source.Expression, + keySelector, + elementSelector, + Expression.Constant(comparer, typeof(IEqualityComparer)) + ) + ); + } + + /// + /// Converts an observable sequence to an enumerable sequence. + /// + /// The type of the elements in the source sequence. + /// An observable sequence to convert to an enumerable sequence. + /// The enumerable sequence containing the elements in the observable sequence. + /// + /// is null. + /// This operator requires the source's object (see ) to implement . + public static IQueryable ToQueryable(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return ((IQueryProvider)source.Provider).CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Creates a list from an observable sequence. + /// + /// The type of the elements in the source sequence. + /// The source observable sequence to get a list of elements for. + /// An observable sequence containing a single element with a list containing all the elements of the source sequence. + /// + /// is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable> ToList(this IQbservable source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression + ) + ); + } + + /// + /// Creates a lookup from an observable sequence according to a specified key selector function. + /// + /// The type of the elements in the source sequence. + /// The type of the lookup key computed for each element in the source sequence. + /// An observable sequence to create a lookup for. + /// A function to extract a key from each element. + /// An observable sequence containing a single element with a lookup mapping unique key values onto the corresponding source sequence's elements. + /// + /// or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable> ToLookup(this IQbservable source, Expression> keySelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey)), + source.Expression, + keySelector + ) + ); + } + + /// + /// Creates a lookup from an observable sequence according to a specified key selector function, and a comparer. + /// + /// The type of the elements in the source sequence. + /// The type of the lookup key computed for each element in the source sequence. + /// An observable sequence to create a lookup for. + /// A function to extract a key from each element. + /// An equality comparer to compare keys. + /// An observable sequence containing a single element with a lookup mapping unique key values onto the corresponding source sequence's elements. + /// + /// or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable> ToLookup(this IQbservable source, Expression> keySelector, IEqualityComparer comparer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey)), + source.Expression, + keySelector, + Expression.Constant(comparer, typeof(IEqualityComparer)) + ) + ); + } + + /// + /// Creates a lookup from an observable sequence according to a specified key selector function, and an element selector function. + /// + /// The type of the elements in the source sequence. + /// The type of the lookup key computed for each element in the source sequence. + /// The type of the lookup value computed for each element in the source sequence. + /// An observable sequence to create a lookup for. + /// A function to extract a key from each element. + /// A transform function to produce a result element value from each element. + /// An observable sequence containing a single element with a lookup mapping unique key values onto the corresponding source sequence's elements. + /// + /// or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable> ToLookup(this IQbservable source, Expression> keySelector, Expression> elementSelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (elementSelector == null) + throw new ArgumentNullException(nameof(elementSelector)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey), typeof(TElement)), + source.Expression, + keySelector, + elementSelector + ) + ); + } + + /// + /// Creates a lookup from an observable sequence according to a specified key selector function, a comparer, and an element selector function. + /// + /// The type of the elements in the source sequence. + /// The type of the lookup key computed for each element in the source sequence. + /// The type of the lookup value computed for each element in the source sequence. + /// An observable sequence to create a lookup for. + /// A function to extract a key from each element. + /// A transform function to produce a result element value from each element. + /// An equality comparer to compare keys. + /// An observable sequence containing a single element with a lookup mapping unique key values onto the corresponding source sequence's elements. + /// + /// or or or is null. + /// The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior. + public static IQbservable> ToLookup(this IQbservable source, Expression> keySelector, Expression> elementSelector, IEqualityComparer comparer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (elementSelector == null) + throw new ArgumentNullException(nameof(elementSelector)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TKey), typeof(TElement)), + source.Expression, + keySelector, + elementSelector, + Expression.Constant(comparer, typeof(IEqualityComparer)) + ) + ); + } + + /// + /// Converts an enumerable sequence to an observable sequence. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequence. + /// Enumerable sequence to convert to an observable sequence. + /// The observable sequence whose elements are pulled from the given enumerable sequence. + /// + /// is null. + public static IQbservable ToObservable(this IQbservableProvider provider, IEnumerable source) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + GetSourceExpression(source) + ) + ); + } + + /// + /// Converts an enumerable sequence to an observable sequence, using the specified scheduler to run the enumeration loop. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequence. + /// Enumerable sequence to convert to an observable sequence. + /// Scheduler to run the enumeration of the input sequence on. + /// The observable sequence whose elements are pulled from the given enumerable sequence. + /// + /// or is null. + public static IQbservable ToObservable(this IQbservableProvider provider, IEnumerable source, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + GetSourceExpression(source), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Constructs an observable sequence that depends on a resource object, whose lifetime is tied to the resulting observable sequence's lifetime. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the produced sequence. + /// The type of the resource used during the generation of the resulting sequence. Needs to implement . + /// Factory function to obtain a resource object. + /// Factory function to obtain an observable sequence that depends on the obtained resource. + /// An observable sequence whose lifetime controls the lifetime of the dependent resource object. + /// + /// or is null. + public static IQbservable Using(this IQbservableProvider provider, Expression> resourceFactory, Expression>> observableFactory) + where TResource : IDisposable + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (resourceFactory == null) + throw new ArgumentNullException(nameof(resourceFactory)); + if (observableFactory == null) + throw new ArgumentNullException(nameof(observableFactory)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult), typeof(TResource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + resourceFactory, + observableFactory + ) + ); + } + + /// + /// Constructs an observable sequence that depends on a resource object, whose lifetime is tied to the resulting observable sequence's lifetime. The resource is obtained and used through asynchronous methods. + /// The CancellationToken passed to the asynchronous methods is tied to the returned disposable subscription, allowing best-effort cancellation at any stage of the resource acquisition or usage. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the produced sequence. + /// The type of the resource used during the generation of the resulting sequence. Needs to implement . + /// Asynchronous factory function to obtain a resource object. + /// Asynchronous factory function to obtain an observable sequence that depends on the obtained resource. + /// An observable sequence whose lifetime controls the lifetime of the dependent resource object. + /// + /// or is null. + /// This operator is especially useful in conjunction with the asynchronous programming features introduced in C# 5.0 and Visual Basic 11. + /// When a subscription to the resulting sequence is disposed, the CancellationToken that was fed to the asynchronous resource factory and observable factory functions will be signaled. + public static IQbservable Using(this IQbservableProvider provider, Expression>> resourceFactoryAsync, Expression>>> observableFactoryAsync) + where TResource : IDisposable + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (resourceFactoryAsync == null) + throw new ArgumentNullException(nameof(resourceFactoryAsync)); + if (observableFactoryAsync == null) + throw new ArgumentNullException(nameof(observableFactoryAsync)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult), typeof(TResource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + resourceFactoryAsync, + observableFactoryAsync + ) + ); + } + + /// + /// Filters the elements of an observable sequence based on a predicate. + /// + /// The type of the elements in the source sequence. + /// An observable sequence whose elements to filter. + /// A function to test each source element for a condition. + /// An observable sequence that contains elements from the input sequence that satisfy the condition. + /// + /// or is null. + public static IQbservable Where(this IQbservable source, Expression> predicate) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (predicate == null) + throw new ArgumentNullException(nameof(predicate)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + predicate + ) + ); + } + + /// + /// Filters the elements of an observable sequence based on a predicate by incorporating the element's index. + /// + /// The type of the elements in the source sequence. + /// An observable sequence whose elements to filter. + /// A function to test each source element for a condition; the second parameter of the function represents the index of the source element. + /// An observable sequence that contains elements from the input sequence that satisfy the condition. + /// + /// or is null. + public static IQbservable Where(this IQbservable source, Expression> predicate) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (predicate == null) + throw new ArgumentNullException(nameof(predicate)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + predicate + ) + ); + } + + /// + /// Repeats the given as long as the specified holds, where the is evaluated before each repeated is subscribed to. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequence. + /// Source to repeat as long as the function evaluates to true. + /// Condition that will be evaluated before subscription to the , to determine whether repetition of the source is required. + /// The observable sequence obtained by concatenating the sequence as long as the holds. + /// + /// or is null. + public static IQbservable While(this IQbservableProvider provider, Expression> condition, IObservable source) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (condition == null) + throw new ArgumentNullException(nameof(condition)); + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + condition, + GetSourceExpression(source) + ) + ); + } + + /// + /// Projects each element of an observable sequence into consecutive non-overlapping windows which are produced based on element count information. + /// + /// The type of the elements in the source sequence, and in the windows in the result sequence. + /// Source sequence to produce windows over. + /// Length of each window. + /// An observable sequence of windows. + /// + /// is null. + /// + /// is less than or equal to zero. + public static IQbservable> Window(this IQbservable source, int count) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(count, typeof(int)) + ) + ); + } + + /// + /// Projects each element of an observable sequence into zero or more windows which are produced based on element count information. + /// + /// The type of the elements in the source sequence, and in the windows in the result sequence. + /// Source sequence to produce windows over. + /// Length of each window. + /// Number of elements to skip between creation of consecutive windows. + /// An observable sequence of windows. + /// + /// is null. + /// + /// or is less than or equal to zero. + public static IQbservable> Window(this IQbservable source, int count, int skip) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(count, typeof(int)), + Expression.Constant(skip, typeof(int)) + ) + ); + } + + /// + /// Projects each element of an observable sequence into consecutive non-overlapping windows which are produced based on timing information. + /// + /// The type of the elements in the source sequence, and in the windows in the result sequence. + /// Source sequence to produce windows over. + /// Length of each window. + /// The sequence of windows. + /// + /// is null. + /// + /// is less than TimeSpan.Zero. + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create windows as fast as it can. + /// Because all source sequence elements end up in one of the windows, some windows won't have a zero time span. This is a side-effect of the asynchrony introduced + /// by the scheduler, where the action to close the current window and to create a new window may not execute immediately, despite the TimeSpan.Zero due time. + /// + public static IQbservable> Window(this IQbservable source, TimeSpan timeSpan) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(timeSpan, typeof(TimeSpan)) + ) + ); + } + + /// + /// Projects each element of an observable sequence into a window that is completed when either it's full or a given amount of time has elapsed. + /// A useful real-world analogy of this overload is the behavior of a ferry leaving the dock when all seats are taken, or at the scheduled time of departure, whichever event occurs first. + /// + /// The type of the elements in the source sequence, and in the windows in the result sequence. + /// Source sequence to produce windows over. + /// Maximum time length of a window. + /// Maximum element count of a window. + /// An observable sequence of windows. + /// + /// is null. + /// + /// is less than TimeSpan.Zero. -or- is less than or equal to zero. + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create windows as fast as it can. + /// Because all source sequence elements end up in one of the windows, some windows won't have a zero time span. This is a side-effect of the asynchrony introduced + /// by the scheduler, where the action to close the current window and to create a new window may not execute immediately, despite the TimeSpan.Zero due time. + /// + public static IQbservable> Window(this IQbservable source, TimeSpan timeSpan, int count) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(timeSpan, typeof(TimeSpan)), + Expression.Constant(count, typeof(int)) + ) + ); + } + + /// + /// Projects each element of an observable sequence into a window that is completed when either it's full or a given amount of time has elapsed, using the specified scheduler to run timers. + /// A useful real-world analogy of this overload is the behavior of a ferry leaving the dock when all seats are taken, or at the scheduled time of departure, whichever event occurs first. + /// + /// The type of the elements in the source sequence, and in the windows in the result sequence. + /// Source sequence to produce windows over. + /// Maximum time length of a window. + /// Maximum element count of a window. + /// Scheduler to run windowing timers on. + /// An observable sequence of windows. + /// + /// or is null. + /// + /// is less than TimeSpan.Zero. -or- is less than or equal to zero. + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create windows as fast as it can. + /// Because all source sequence elements end up in one of the windows, some windows won't have a zero time span. This is a side-effect of the asynchrony introduced + /// by the scheduler, where the action to close the current window and to create a new window may not execute immediately, despite the TimeSpan.Zero due time. + /// + public static IQbservable> Window(this IQbservable source, TimeSpan timeSpan, int count, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(timeSpan, typeof(TimeSpan)), + Expression.Constant(count, typeof(int)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Projects each element of an observable sequence into consecutive non-overlapping windows which are produced based on timing information, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence, and in the windows in the result sequence. + /// Source sequence to produce windows over. + /// Length of each window. + /// Scheduler to run windowing timers on. + /// An observable sequence of windows. + /// + /// or is null. + /// + /// is less than TimeSpan.Zero. + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create windows as fast as it can. + /// Because all source sequence elements end up in one of the windows, some windows won't have a zero time span. This is a side-effect of the asynchrony introduced + /// by the scheduler, where the action to close the current window and to create a new window may not execute immediately, despite the TimeSpan.Zero due time. + /// + public static IQbservable> Window(this IQbservable source, TimeSpan timeSpan, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(timeSpan, typeof(TimeSpan)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Projects each element of an observable sequence into zero or more windows which are produced based on timing information. + /// + /// The type of the elements in the source sequence, and in the windows in the result sequence. + /// Source sequence to produce windows over. + /// Length of each window. + /// Interval between creation of consecutive windows. + /// An observable sequence of windows. + /// + /// is null. + /// + /// or is less than TimeSpan.Zero. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create windows with minimum duration + /// length. However, some windows won't have a zero time span. This is a side-effect of the asynchrony introduced by the scheduler, where the action to close the + /// current window may not execute immediately, despite the TimeSpan.Zero due time. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create windows as fast as it can. + /// However, this doesn't mean all windows will start at the beginning of the source sequence. This is a side-effect of the asynchrony introduced by the scheduler, + /// where the action to create a new window may not execute immediately, despite the TimeSpan.Zero due time. + /// + /// + public static IQbservable> Window(this IQbservable source, TimeSpan timeSpan, TimeSpan timeShift) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(timeSpan, typeof(TimeSpan)), + Expression.Constant(timeShift, typeof(TimeSpan)) + ) + ); + } + + /// + /// Projects each element of an observable sequence into zero or more windows which are produced based on timing information, using the specified scheduler to run timers. + /// + /// The type of the elements in the source sequence, and in the windows in the result sequence. + /// Source sequence to produce windows over. + /// Length of each window. + /// Interval between creation of consecutive windows. + /// Scheduler to run windowing timers on. + /// An observable sequence of windows. + /// + /// or is null. + /// + /// or is less than TimeSpan.Zero. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create windows with minimum duration + /// length. However, some windows won't have a zero time span. This is a side-effect of the asynchrony introduced by the scheduler, where the action to close the + /// current window may not execute immediately, despite the TimeSpan.Zero due time. + /// + /// + /// Specifying a TimeSpan.Zero value for is not recommended but supported, causing the scheduler to create windows as fast as it can. + /// However, this doesn't mean all windows will start at the beginning of the source sequence. This is a side-effect of the asynchrony introduced by the scheduler, + /// where the action to create a new window may not execute immediately, despite the TimeSpan.Zero due time. + /// + /// + public static IQbservable> Window(this IQbservable source, TimeSpan timeSpan, TimeSpan timeShift, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + source.Expression, + Expression.Constant(timeSpan, typeof(TimeSpan)), + Expression.Constant(timeShift, typeof(TimeSpan)), + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } + + /// + /// Projects each element of an observable sequence into consecutive non-overlapping windows. + /// + /// The type of the elements in the source sequence, and in the windows in the result sequence. + /// The type of the elements in the sequences indicating window boundary events. + /// Source sequence to produce windows over. + /// Sequence of window boundary markers. The current window is closed and a new window is opened upon receiving a boundary marker. + /// An observable sequence of windows. + /// + /// or is null. + public static IQbservable> Window(this IQbservable source, IObservable windowBoundaries) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (windowBoundaries == null) + throw new ArgumentNullException(nameof(windowBoundaries)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TWindowBoundary)), + source.Expression, + GetSourceExpression(windowBoundaries) + ) + ); + } + + /// + /// Projects each element of an observable sequence into consecutive non-overlapping windows. + /// + /// The type of the elements in the source sequence, and in the windows in the result sequence. + /// The type of the elements in the sequences indicating window closing events. + /// Source sequence to produce windows over. + /// A function invoked to define the boundaries of the produced windows. A new window is started when the previous one is closed. + /// An observable sequence of windows. + /// + /// or is null. + public static IQbservable> Window(this IQbservable source, Expression>> windowClosingSelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (windowClosingSelector == null) + throw new ArgumentNullException(nameof(windowClosingSelector)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TWindowClosing)), + source.Expression, + windowClosingSelector + ) + ); + } + + /// + /// Projects each element of an observable sequence into zero or more windows. + /// + /// The type of the elements in the source sequence, and in the windows in the result sequence. + /// The type of the elements in the sequence indicating window opening events, also passed to the closing selector to obtain a sequence of window closing events. + /// The type of the elements in the sequences indicating window closing events. + /// Source sequence to produce windows over. + /// Observable sequence whose elements denote the creation of new windows. + /// A function invoked to define the closing of each produced window. + /// An observable sequence of windows. + /// + /// or or is null. + public static IQbservable> Window(this IQbservable source, IObservable windowOpenings, Expression>> windowClosingSelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (windowOpenings == null) + throw new ArgumentNullException(nameof(windowOpenings)); + if (windowClosingSelector == null) + throw new ArgumentNullException(nameof(windowClosingSelector)); + + return source.Provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TWindowOpening), typeof(TWindowClosing)), + source.Expression, + GetSourceExpression(windowOpenings), + windowClosingSelector + ) + ); + } + + /// + /// Merges two observable sequences into one observable sequence by combining each element from the first source with the latest element from the second source, if any. + /// Starting from Rx.NET 4.0, this will subscribe to before subscribing to to have a latest element readily available + /// in case emits an element right away. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Function to invoke for each element from the first source combined with the latest element from the second source, if any. + /// An observable sequence containing the result of combining each element of the first source with the latest element from the second source, if any, using the specified result selector function. + /// + /// or or is null. + public static IQbservable WithLatestFrom(this IQbservable first, IObservable second, Expression> resultSelector) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return first.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TResult)), + first.Expression, + GetSourceExpression(second), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by emitting a list with the elements of the observable sequences at corresponding indexes. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequences, and in the lists in the result sequence. + /// Observable sources. + /// An observable sequence containing lists of elements at corresponding indexes. + /// + /// is null. + public static IQbservable> Zip(this IQbservableProvider provider, IEnumerable> sources) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + GetSourceExpression(sources) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by emitting a list with the elements of the observable sequences at corresponding indexes. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequences, and in the lists in the result sequence. + /// Observable sources. + /// An observable sequence containing lists of elements at corresponding indexes. + /// + /// is null. + public static IQbservable> Zip(this IQbservableProvider provider, params IObservable[] sources) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return provider.CreateQuery>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + GetSourceExpression(sources) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequences. + /// The type of the elements in the result sequence, returned by the selector function. + /// Observable sources. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or is null. + public static IQbservable Zip(this IQbservableProvider provider, IEnumerable> sources, Expression, TResult>> resultSelector) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + GetSourceExpression(sources), + resultSelector + ) + ); + } + + /// + /// Merges two observable sequences into one observable sequence by combining their elements in a pairwise fashion. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Function to invoke for each consecutive pair of elements from the first and second source. + /// An observable sequence containing the result of pairwise combining the elements of the first and second source using the specified result selector function. + /// + /// or or is null. + public static IQbservable Zip(this IQbservable first, IObservable second, Expression> resultSelector) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return first.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TResult)), + first.Expression, + GetSourceExpression(second), + resultSelector + ) + ); + } + + /// + /// Merges an observable sequence and an enumerable sequence into one observable sequence by using the selector function. + /// + /// The type of the elements in the first observable source sequence. + /// The type of the elements in the second enumerable source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second enumerable source. + /// Function to invoke for each consecutive pair of elements from the first and second source. + /// An observable sequence containing the result of pairwise combining the elements of the first and second source using the specified result selector function. + /// + /// or or is null. + public static IQbservable Zip(this IQbservable first, IEnumerable second, Expression> resultSelector) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return first.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TResult)), + first.Expression, + GetSourceExpression(second), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or is null. + public static IQbservable Zip(this IQbservable source1, IObservable source2, IObservable source3, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or is null. + public static IQbservable Zip(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or or is null. + public static IQbservable Zip(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (source5 == null) + throw new ArgumentNullException(nameof(source5)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TSource5), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + GetSourceExpression(source5), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or or or is null. + public static IQbservable Zip(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (source5 == null) + throw new ArgumentNullException(nameof(source5)); + if (source6 == null) + throw new ArgumentNullException(nameof(source6)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TSource5), typeof(TSource6), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + GetSourceExpression(source5), + GetSourceExpression(source6), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or or or or is null. + public static IQbservable Zip(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (source5 == null) + throw new ArgumentNullException(nameof(source5)); + if (source6 == null) + throw new ArgumentNullException(nameof(source6)); + if (source7 == null) + throw new ArgumentNullException(nameof(source7)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TSource5), typeof(TSource6), typeof(TSource7), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + GetSourceExpression(source5), + GetSourceExpression(source6), + GetSourceExpression(source7), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or or or or or is null. + public static IQbservable Zip(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (source5 == null) + throw new ArgumentNullException(nameof(source5)); + if (source6 == null) + throw new ArgumentNullException(nameof(source6)); + if (source7 == null) + throw new ArgumentNullException(nameof(source7)); + if (source8 == null) + throw new ArgumentNullException(nameof(source8)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TSource5), typeof(TSource6), typeof(TSource7), typeof(TSource8), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + GetSourceExpression(source5), + GetSourceExpression(source6), + GetSourceExpression(source7), + GetSourceExpression(source8), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or or or or or or is null. + public static IQbservable Zip(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (source5 == null) + throw new ArgumentNullException(nameof(source5)); + if (source6 == null) + throw new ArgumentNullException(nameof(source6)); + if (source7 == null) + throw new ArgumentNullException(nameof(source7)); + if (source8 == null) + throw new ArgumentNullException(nameof(source8)); + if (source9 == null) + throw new ArgumentNullException(nameof(source9)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TSource5), typeof(TSource6), typeof(TSource7), typeof(TSource8), typeof(TSource9), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + GetSourceExpression(source5), + GetSourceExpression(source6), + GetSourceExpression(source7), + GetSourceExpression(source8), + GetSourceExpression(source9), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or or or or or or or is null. + public static IQbservable Zip(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (source5 == null) + throw new ArgumentNullException(nameof(source5)); + if (source6 == null) + throw new ArgumentNullException(nameof(source6)); + if (source7 == null) + throw new ArgumentNullException(nameof(source7)); + if (source8 == null) + throw new ArgumentNullException(nameof(source8)); + if (source9 == null) + throw new ArgumentNullException(nameof(source9)); + if (source10 == null) + throw new ArgumentNullException(nameof(source10)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TSource5), typeof(TSource6), typeof(TSource7), typeof(TSource8), typeof(TSource9), typeof(TSource10), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + GetSourceExpression(source5), + GetSourceExpression(source6), + GetSourceExpression(source7), + GetSourceExpression(source8), + GetSourceExpression(source9), + GetSourceExpression(source10), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or or or or or or or or is null. + public static IQbservable Zip(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (source5 == null) + throw new ArgumentNullException(nameof(source5)); + if (source6 == null) + throw new ArgumentNullException(nameof(source6)); + if (source7 == null) + throw new ArgumentNullException(nameof(source7)); + if (source8 == null) + throw new ArgumentNullException(nameof(source8)); + if (source9 == null) + throw new ArgumentNullException(nameof(source9)); + if (source10 == null) + throw new ArgumentNullException(nameof(source10)); + if (source11 == null) + throw new ArgumentNullException(nameof(source11)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TSource5), typeof(TSource6), typeof(TSource7), typeof(TSource8), typeof(TSource9), typeof(TSource10), typeof(TSource11), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + GetSourceExpression(source5), + GetSourceExpression(source6), + GetSourceExpression(source7), + GetSourceExpression(source8), + GetSourceExpression(source9), + GetSourceExpression(source10), + GetSourceExpression(source11), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or or or or or or or or or is null. + public static IQbservable Zip(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (source5 == null) + throw new ArgumentNullException(nameof(source5)); + if (source6 == null) + throw new ArgumentNullException(nameof(source6)); + if (source7 == null) + throw new ArgumentNullException(nameof(source7)); + if (source8 == null) + throw new ArgumentNullException(nameof(source8)); + if (source9 == null) + throw new ArgumentNullException(nameof(source9)); + if (source10 == null) + throw new ArgumentNullException(nameof(source10)); + if (source11 == null) + throw new ArgumentNullException(nameof(source11)); + if (source12 == null) + throw new ArgumentNullException(nameof(source12)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TSource5), typeof(TSource6), typeof(TSource7), typeof(TSource8), typeof(TSource9), typeof(TSource10), typeof(TSource11), typeof(TSource12), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + GetSourceExpression(source5), + GetSourceExpression(source6), + GetSourceExpression(source7), + GetSourceExpression(source8), + GetSourceExpression(source9), + GetSourceExpression(source10), + GetSourceExpression(source11), + GetSourceExpression(source12), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Thirteenth observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or or or or or or or or or or is null. + public static IQbservable Zip(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (source5 == null) + throw new ArgumentNullException(nameof(source5)); + if (source6 == null) + throw new ArgumentNullException(nameof(source6)); + if (source7 == null) + throw new ArgumentNullException(nameof(source7)); + if (source8 == null) + throw new ArgumentNullException(nameof(source8)); + if (source9 == null) + throw new ArgumentNullException(nameof(source9)); + if (source10 == null) + throw new ArgumentNullException(nameof(source10)); + if (source11 == null) + throw new ArgumentNullException(nameof(source11)); + if (source12 == null) + throw new ArgumentNullException(nameof(source12)); + if (source13 == null) + throw new ArgumentNullException(nameof(source13)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TSource5), typeof(TSource6), typeof(TSource7), typeof(TSource8), typeof(TSource9), typeof(TSource10), typeof(TSource11), typeof(TSource12), typeof(TSource13), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + GetSourceExpression(source5), + GetSourceExpression(source6), + GetSourceExpression(source7), + GetSourceExpression(source8), + GetSourceExpression(source9), + GetSourceExpression(source10), + GetSourceExpression(source11), + GetSourceExpression(source12), + GetSourceExpression(source13), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the fourteenth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Thirteenth observable source. + /// Fourteenth observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or or or or or or or or or or or is null. + public static IQbservable Zip(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (source5 == null) + throw new ArgumentNullException(nameof(source5)); + if (source6 == null) + throw new ArgumentNullException(nameof(source6)); + if (source7 == null) + throw new ArgumentNullException(nameof(source7)); + if (source8 == null) + throw new ArgumentNullException(nameof(source8)); + if (source9 == null) + throw new ArgumentNullException(nameof(source9)); + if (source10 == null) + throw new ArgumentNullException(nameof(source10)); + if (source11 == null) + throw new ArgumentNullException(nameof(source11)); + if (source12 == null) + throw new ArgumentNullException(nameof(source12)); + if (source13 == null) + throw new ArgumentNullException(nameof(source13)); + if (source14 == null) + throw new ArgumentNullException(nameof(source14)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TSource5), typeof(TSource6), typeof(TSource7), typeof(TSource8), typeof(TSource9), typeof(TSource10), typeof(TSource11), typeof(TSource12), typeof(TSource13), typeof(TSource14), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + GetSourceExpression(source5), + GetSourceExpression(source6), + GetSourceExpression(source7), + GetSourceExpression(source8), + GetSourceExpression(source9), + GetSourceExpression(source10), + GetSourceExpression(source11), + GetSourceExpression(source12), + GetSourceExpression(source13), + GetSourceExpression(source14), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the fourteenth source sequence. + /// The type of the elements in the fifteenth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Thirteenth observable source. + /// Fourteenth observable source. + /// Fifteenth observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or or or or or or or or or or or or is null. + public static IQbservable Zip(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, IObservable source15, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (source5 == null) + throw new ArgumentNullException(nameof(source5)); + if (source6 == null) + throw new ArgumentNullException(nameof(source6)); + if (source7 == null) + throw new ArgumentNullException(nameof(source7)); + if (source8 == null) + throw new ArgumentNullException(nameof(source8)); + if (source9 == null) + throw new ArgumentNullException(nameof(source9)); + if (source10 == null) + throw new ArgumentNullException(nameof(source10)); + if (source11 == null) + throw new ArgumentNullException(nameof(source11)); + if (source12 == null) + throw new ArgumentNullException(nameof(source12)); + if (source13 == null) + throw new ArgumentNullException(nameof(source13)); + if (source14 == null) + throw new ArgumentNullException(nameof(source14)); + if (source15 == null) + throw new ArgumentNullException(nameof(source15)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TSource5), typeof(TSource6), typeof(TSource7), typeof(TSource8), typeof(TSource9), typeof(TSource10), typeof(TSource11), typeof(TSource12), typeof(TSource13), typeof(TSource14), typeof(TSource15), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + GetSourceExpression(source5), + GetSourceExpression(source6), + GetSourceExpression(source7), + GetSourceExpression(source8), + GetSourceExpression(source9), + GetSourceExpression(source10), + GetSourceExpression(source11), + GetSourceExpression(source12), + GetSourceExpression(source13), + GetSourceExpression(source14), + GetSourceExpression(source15), + resultSelector + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the fourteenth source sequence. + /// The type of the elements in the fifteenth source sequence. + /// The type of the elements in the sixteenth source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Thirteenth observable source. + /// Fourteenth observable source. + /// Fifteenth observable source. + /// Sixteenth observable source. + /// Function to invoke for each series of elements at corresponding indexes in the sources. + /// An observable sequence containing the result of combining elements of the sources using the specified result selector function. + /// + /// or or or or or or or or or or or or or or or or is null. + public static IQbservable Zip(this IQbservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, IObservable source15, IObservable source16, Expression> resultSelector) + { + if (source1 == null) + throw new ArgumentNullException(nameof(source1)); + if (source2 == null) + throw new ArgumentNullException(nameof(source2)); + if (source3 == null) + throw new ArgumentNullException(nameof(source3)); + if (source4 == null) + throw new ArgumentNullException(nameof(source4)); + if (source5 == null) + throw new ArgumentNullException(nameof(source5)); + if (source6 == null) + throw new ArgumentNullException(nameof(source6)); + if (source7 == null) + throw new ArgumentNullException(nameof(source7)); + if (source8 == null) + throw new ArgumentNullException(nameof(source8)); + if (source9 == null) + throw new ArgumentNullException(nameof(source9)); + if (source10 == null) + throw new ArgumentNullException(nameof(source10)); + if (source11 == null) + throw new ArgumentNullException(nameof(source11)); + if (source12 == null) + throw new ArgumentNullException(nameof(source12)); + if (source13 == null) + throw new ArgumentNullException(nameof(source13)); + if (source14 == null) + throw new ArgumentNullException(nameof(source14)); + if (source15 == null) + throw new ArgumentNullException(nameof(source15)); + if (source16 == null) + throw new ArgumentNullException(nameof(source16)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return source1.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TSource3), typeof(TSource4), typeof(TSource5), typeof(TSource6), typeof(TSource7), typeof(TSource8), typeof(TSource9), typeof(TSource10), typeof(TSource11), typeof(TSource12), typeof(TSource13), typeof(TSource14), typeof(TSource15), typeof(TSource16), typeof(TResult)), + source1.Expression, + GetSourceExpression(source2), + GetSourceExpression(source3), + GetSourceExpression(source4), + GetSourceExpression(source5), + GetSourceExpression(source6), + GetSourceExpression(source7), + GetSourceExpression(source8), + GetSourceExpression(source9), + GetSourceExpression(source10), + GetSourceExpression(source11), + GetSourceExpression(source12), + GetSourceExpression(source13), + GetSourceExpression(source14), + GetSourceExpression(source15), + GetSourceExpression(source16), + resultSelector + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression action) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + + var m = (MethodInfo)MethodInfo.GetCurrentMethod()!; + return () => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action + ) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression action, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = (MethodInfo)MethodInfo.GetCurrentMethod()!; + return () => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1)); + return (t1) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action + ), + Expression.Constant(t1, typeof(TArg1)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1)); + return (t1) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2)); + return (t1, t2) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2)); + return (t1, t2) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3)); + return (t1, t2, t3) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3)); + return (t1, t2, t3) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4)); + return (t1, t2, t3, t4) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4)); + return (t1, t2, t3, t4) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5)); + return (t1, t2, t3, t4, t5) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5)); + return (t1, t2, t3, t4, t5) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6)); + return (t1, t2, t3, t4, t5, t6) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6)); + return (t1, t2, t3, t4, t5, t6) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7)); + return (t1, t2, t3, t4, t5, t6, t7) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7)); + return (t1, t2, t3, t4, t5, t6, t7) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8)); + return (t1, t2, t3, t4, t5, t6, t7, t8) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8)); + return (t1, t2, t3, t4, t5, t6, t7, t8) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// The type of the eleventh argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// The type of the eleventh argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// The type of the eleventh argument passed to the action. + /// The type of the twelfth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// The type of the eleventh argument passed to the action. + /// The type of the twelfth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// The type of the eleventh argument passed to the action. + /// The type of the twelfth argument passed to the action. + /// The type of the thirteenth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12), typeof(TArg13)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)), + Expression.Constant(t13, typeof(TArg13)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// The type of the eleventh argument passed to the action. + /// The type of the twelfth argument passed to the action. + /// The type of the thirteenth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12), typeof(TArg13)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)), + Expression.Constant(t13, typeof(TArg13)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// The type of the eleventh argument passed to the action. + /// The type of the twelfth argument passed to the action. + /// The type of the thirteenth argument passed to the action. + /// The type of the fourteenth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12), typeof(TArg13), typeof(TArg14)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)), + Expression.Constant(t13, typeof(TArg13)), + Expression.Constant(t14, typeof(TArg14)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// The type of the eleventh argument passed to the action. + /// The type of the twelfth argument passed to the action. + /// The type of the thirteenth argument passed to the action. + /// The type of the fourteenth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12), typeof(TArg13), typeof(TArg14)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)), + Expression.Constant(t13, typeof(TArg13)), + Expression.Constant(t14, typeof(TArg14)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// The type of the eleventh argument passed to the action. + /// The type of the twelfth argument passed to the action. + /// The type of the thirteenth argument passed to the action. + /// The type of the fourteenth argument passed to the action. + /// The type of the fifteenth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12), typeof(TArg13), typeof(TArg14), typeof(TArg15)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)), + Expression.Constant(t13, typeof(TArg13)), + Expression.Constant(t14, typeof(TArg14)), + Expression.Constant(t15, typeof(TArg15)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// The type of the eleventh argument passed to the action. + /// The type of the twelfth argument passed to the action. + /// The type of the thirteenth argument passed to the action. + /// The type of the fourteenth argument passed to the action. + /// The type of the fifteenth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12), typeof(TArg13), typeof(TArg14), typeof(TArg15)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)), + Expression.Constant(t13, typeof(TArg13)), + Expression.Constant(t14, typeof(TArg14)), + Expression.Constant(t15, typeof(TArg15)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the default scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// The type of the eleventh argument passed to the action. + /// The type of the twelfth argument passed to the action. + /// The type of the thirteenth argument passed to the action. + /// The type of the fourteenth argument passed to the action. + /// The type of the fifteenth argument passed to the action. + /// The type of the sixteenth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Asynchronous action. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12), typeof(TArg13), typeof(TArg14), typeof(TArg15), typeof(TArg16)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)), + Expression.Constant(t13, typeof(TArg13)), + Expression.Constant(t14, typeof(TArg14)), + Expression.Constant(t15, typeof(TArg15)), + Expression.Constant(t16, typeof(TArg16)) + ) + ); + } + + /// + /// Converts the function into an asynchronous action. Each invocation of the resulting asynchronous action causes an invocation of the original synchronous action on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the action. + /// The type of the second argument passed to the action. + /// The type of the third argument passed to the action. + /// The type of the fourth argument passed to the action. + /// The type of the fifth argument passed to the action. + /// The type of the sixth argument passed to the action. + /// The type of the seventh argument passed to the action. + /// The type of the eighth argument passed to the action. + /// The type of the ninth argument passed to the action. + /// The type of the tenth argument passed to the action. + /// The type of the eleventh argument passed to the action. + /// The type of the twelfth argument passed to the action. + /// The type of the thirteenth argument passed to the action. + /// The type of the fourteenth argument passed to the action. + /// The type of the fifteenth argument passed to the action. + /// The type of the sixteenth argument passed to the action. + /// Action to convert to an asynchronous action. + /// Scheduler to invoke the original action on. + /// Asynchronous action. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> action, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (action == null) + throw new ArgumentNullException(nameof(action)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12), typeof(TArg13), typeof(TArg14), typeof(TArg15), typeof(TArg16)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + action, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)), + Expression.Constant(t13, typeof(TArg13)), + Expression.Constant(t14, typeof(TArg14)), + Expression.Constant(t15, typeof(TArg15)), + Expression.Constant(t16, typeof(TArg16)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)); + return () => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function + ) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)); + return () => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TResult)); + return (t1) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function + ), + Expression.Constant(t1, typeof(TArg1)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TResult)); + return (t1) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TResult)); + return (t1, t2) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TResult)); + return (t1, t2) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TResult)); + return (t1, t2, t3) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TResult)); + return (t1, t2, t3) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TResult)); + return (t1, t2, t3, t4) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TResult)); + return (t1, t2, t3, t4) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TResult)); + return (t1, t2, t3, t4, t5) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TResult)); + return (t1, t2, t3, t4, t5) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the eleventh argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the eleventh argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the eleventh argument passed to the function. + /// The type of the twelfth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the eleventh argument passed to the function. + /// The type of the twelfth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the eleventh argument passed to the function. + /// The type of the twelfth argument passed to the function. + /// The type of the thirteenth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12), typeof(TArg13), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)), + Expression.Constant(t13, typeof(TArg13)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the eleventh argument passed to the function. + /// The type of the twelfth argument passed to the function. + /// The type of the thirteenth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12), typeof(TArg13), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)), + Expression.Constant(t13, typeof(TArg13)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the eleventh argument passed to the function. + /// The type of the twelfth argument passed to the function. + /// The type of the thirteenth argument passed to the function. + /// The type of the fourteenth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12), typeof(TArg13), typeof(TArg14), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)), + Expression.Constant(t13, typeof(TArg13)), + Expression.Constant(t14, typeof(TArg14)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the eleventh argument passed to the function. + /// The type of the twelfth argument passed to the function. + /// The type of the thirteenth argument passed to the function. + /// The type of the fourteenth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12), typeof(TArg13), typeof(TArg14), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)), + Expression.Constant(t13, typeof(TArg13)), + Expression.Constant(t14, typeof(TArg14)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the eleventh argument passed to the function. + /// The type of the twelfth argument passed to the function. + /// The type of the thirteenth argument passed to the function. + /// The type of the fourteenth argument passed to the function. + /// The type of the fifteenth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12), typeof(TArg13), typeof(TArg14), typeof(TArg15), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)), + Expression.Constant(t13, typeof(TArg13)), + Expression.Constant(t14, typeof(TArg14)), + Expression.Constant(t15, typeof(TArg15)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the eleventh argument passed to the function. + /// The type of the twelfth argument passed to the function. + /// The type of the thirteenth argument passed to the function. + /// The type of the fourteenth argument passed to the function. + /// The type of the fifteenth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12), typeof(TArg13), typeof(TArg14), typeof(TArg15), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)), + Expression.Constant(t13, typeof(TArg13)), + Expression.Constant(t14, typeof(TArg14)), + Expression.Constant(t15, typeof(TArg15)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the eleventh argument passed to the function. + /// The type of the twelfth argument passed to the function. + /// The type of the thirteenth argument passed to the function. + /// The type of the fourteenth argument passed to the function. + /// The type of the fifteenth argument passed to the function. + /// The type of the sixteenth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Asynchronous function. + /// + /// is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12), typeof(TArg13), typeof(TArg14), typeof(TArg15), typeof(TArg16), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)), + Expression.Constant(t13, typeof(TArg13)), + Expression.Constant(t14, typeof(TArg14)), + Expression.Constant(t15, typeof(TArg15)), + Expression.Constant(t16, typeof(TArg16)) + ) + ); + } + + /// + /// Converts the function into an asynchronous function. Each invocation of the resulting asynchronous function causes an invocation of the original synchronous function on the specified scheduler. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the function. + /// The type of the second argument passed to the function. + /// The type of the third argument passed to the function. + /// The type of the fourth argument passed to the function. + /// The type of the fifth argument passed to the function. + /// The type of the sixth argument passed to the function. + /// The type of the seventh argument passed to the function. + /// The type of the eighth argument passed to the function. + /// The type of the ninth argument passed to the function. + /// The type of the tenth argument passed to the function. + /// The type of the eleventh argument passed to the function. + /// The type of the twelfth argument passed to the function. + /// The type of the thirteenth argument passed to the function. + /// The type of the fourteenth argument passed to the function. + /// The type of the fifteenth argument passed to the function. + /// The type of the sixteenth argument passed to the function. + /// The type of the result returned by the function. + /// Function to convert to an asynchronous function. + /// Scheduler to invoke the original function on. + /// Asynchronous function. + /// + /// or is null. + public static Func> ToAsync(this IQbservableProvider provider, Expression> function, IScheduler scheduler) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (function == null) + throw new ArgumentNullException(nameof(function)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12), typeof(TArg13), typeof(TArg14), typeof(TArg15), typeof(TArg16), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + function, + Expression.Constant(scheduler, typeof(IScheduler)) + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)), + Expression.Constant(t13, typeof(TArg13)), + Expression.Constant(t14, typeof(TArg14)), + Expression.Constant(t15, typeof(TArg15)), + Expression.Constant(t16, typeof(TArg16)) + ) + ); + } + + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = (MethodInfo)MethodInfo.GetCurrentMethod()!; + return () => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1)); + return (t1) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2)); + return (t1, t2) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3)); + return (t1, t2, t3) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4)); + return (t1, t2, t3, t4) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5)); + return (t1, t2, t3, t4, t5) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6)); + return (t1, t2, t3, t4, t5, t6) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7)); + return (t1, t2, t3, t4, t5, t6, t7) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8)); + return (t1, t2, t3, t4, t5, t6, t7, t8) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the ninth argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the ninth argument passed to the begin delegate. + /// The type of the tenth argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the ninth argument passed to the begin delegate. + /// The type of the tenth argument passed to the begin delegate. + /// The type of the eleventh argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the ninth argument passed to the begin delegate. + /// The type of the tenth argument passed to the begin delegate. + /// The type of the eleventh argument passed to the begin delegate. + /// The type of the twelfth argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the ninth argument passed to the begin delegate. + /// The type of the tenth argument passed to the begin delegate. + /// The type of the eleventh argument passed to the begin delegate. + /// The type of the twelfth argument passed to the begin delegate. + /// The type of the thirteenth argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12), typeof(TArg13)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)), + Expression.Constant(t13, typeof(TArg13)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the ninth argument passed to the begin delegate. + /// The type of the tenth argument passed to the begin delegate. + /// The type of the eleventh argument passed to the begin delegate. + /// The type of the twelfth argument passed to the begin delegate. + /// The type of the thirteenth argument passed to the begin delegate. + /// The type of the fourteenth argument passed to the begin delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result (represented as a Unit value) as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12), typeof(TArg13), typeof(TArg14)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)), + Expression.Constant(t13, typeof(TArg13)), + Expression.Constant(t14, typeof(TArg14)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)); + return () => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TResult)); + return (t1) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TResult)); + return (t1, t2) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TResult)); + return (t1, t2, t3) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TResult)); + return (t1, t2, t3, t4) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TResult)); + return (t1, t2, t3, t4, t5) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the ninth argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the ninth argument passed to the begin delegate. + /// The type of the tenth argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the ninth argument passed to the begin delegate. + /// The type of the tenth argument passed to the begin delegate. + /// The type of the eleventh argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the ninth argument passed to the begin delegate. + /// The type of the tenth argument passed to the begin delegate. + /// The type of the eleventh argument passed to the begin delegate. + /// The type of the twelfth argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the ninth argument passed to the begin delegate. + /// The type of the tenth argument passed to the begin delegate. + /// The type of the eleventh argument passed to the begin delegate. + /// The type of the twelfth argument passed to the begin delegate. + /// The type of the thirteenth argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12), typeof(TArg13), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)), + Expression.Constant(t13, typeof(TArg13)) + ) + ); + } + + /// + /// Converts a Begin/End invoke function pair into an asynchronous function. + /// + /// Query provider used to construct the data source. + /// The type of the first argument passed to the begin delegate. + /// The type of the second argument passed to the begin delegate. + /// The type of the third argument passed to the begin delegate. + /// The type of the fourth argument passed to the begin delegate. + /// The type of the fifth argument passed to the begin delegate. + /// The type of the sixth argument passed to the begin delegate. + /// The type of the seventh argument passed to the begin delegate. + /// The type of the eighth argument passed to the begin delegate. + /// The type of the ninth argument passed to the begin delegate. + /// The type of the tenth argument passed to the begin delegate. + /// The type of the eleventh argument passed to the begin delegate. + /// The type of the twelfth argument passed to the begin delegate. + /// The type of the thirteenth argument passed to the begin delegate. + /// The type of the fourteenth argument passed to the begin delegate. + /// The type of the result returned by the end delegate. + /// The delegate that begins the asynchronous operation. + /// The delegate that ends the asynchronous operation. + /// Function that can be used to start the asynchronous operation and retrieve the result as an observable sequence. + /// + /// or is null. + /// Each invocation of the resulting function will cause the asynchronous operation to be started. Subscription to the resulting sequence has no observable side-effect, and each subscription will produce the asynchronous operation's result. +#if PREFERASYNC + [Obsolete(Constants_Linq.USE_TASK_FROMASYNCPATTERN)] +#endif + public static Func> FromAsyncPattern(this IQbservableProvider provider, Expression> begin, Expression> end) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (begin == null) + throw new ArgumentNullException(nameof(begin)); + if (end == null) + throw new ArgumentNullException(nameof(end)); + + var m = ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TArg1), typeof(TArg2), typeof(TArg3), typeof(TArg4), typeof(TArg5), typeof(TArg6), typeof(TArg7), typeof(TArg8), typeof(TArg9), typeof(TArg10), typeof(TArg11), typeof(TArg12), typeof(TArg13), typeof(TArg14), typeof(TResult)); + return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14) => provider.CreateQuery( + Expression.Invoke( + Expression.Call( + null, + m, + Expression.Constant(provider, typeof(IQbservableProvider)), + begin, + end + ), + Expression.Constant(t1, typeof(TArg1)), + Expression.Constant(t2, typeof(TArg2)), + Expression.Constant(t3, typeof(TArg3)), + Expression.Constant(t4, typeof(TArg4)), + Expression.Constant(t5, typeof(TArg5)), + Expression.Constant(t6, typeof(TArg6)), + Expression.Constant(t7, typeof(TArg7)), + Expression.Constant(t8, typeof(TArg8)), + Expression.Constant(t9, typeof(TArg9)), + Expression.Constant(t10, typeof(TArg10)), + Expression.Constant(t11, typeof(TArg11)), + Expression.Constant(t12, typeof(TArg12)), + Expression.Constant(t13, typeof(TArg13)), + Expression.Constant(t14, typeof(TArg14)) + ) + ); + } + + } +} + +#pragma warning restore 1591 + diff --git a/LibExternal/System.Reactive/Linq/Qbservable.Joins.cs b/LibExternal/System.Reactive/Linq/Qbservable.Joins.cs new file mode 100644 index 0000000..42411b6 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Qbservable.Joins.cs @@ -0,0 +1,150 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reactive.Joins; +using System.Reflection; + +namespace System.Reactive.Linq +{ + public static partial class Qbservable + { + /* NOTE: Keep XML docs consistent with the corresponding Observable methods (modulo the IQbservableProvider parameters of course). */ + + /// + /// Creates a pattern that matches when both observable sequences have an available element. + /// + /// The type of the elements in the left sequence. + /// The type of the elements in the right sequence. + /// Observable sequence to match with the right sequence. + /// Observable sequence to match with the left sequence. + /// Pattern object that matches when both observable sequences have an available element. + /// or is null. + public static QueryablePattern And(this IQbservable left, IObservable right) + { + if (left == null) + { + throw new ArgumentNullException(nameof(left)); + } + + if (right == null) + { + throw new ArgumentNullException(nameof(right)); + } + + return new QueryablePattern( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodBase.GetCurrentMethod()!).MakeGenericMethod(typeof(TLeft), typeof(TRight)), +#pragma warning restore IL2060 + left.Expression, + GetSourceExpression(right) + ) + ); + } + + /// + /// Matches when the observable sequence has an available element and projects the element by invoking the selector function. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// Observable sequence to apply the selector on. + /// Selector that will be invoked for elements in the source sequence. + /// Plan that produces the projected results, to be fed (with other plans) to the When operator. + /// or is null. + public static QueryablePlan Then(this IQbservable source, Expression> selector) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return new QueryablePlan( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodBase.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource), typeof(TResult)), +#pragma warning restore IL2060 + source.Expression, + selector + ) + ); + } + + /// + /// Joins together the results from several patterns. + /// + /// The type of the elements in the result sequence, obtained from the specified patterns. + /// Query provider used to construct the data source. + /// A series of plans created by use of the Then operator on patterns. + /// An observable sequence with the results from matching several patterns. + /// or is null. + public static IQbservable When(this IQbservableProvider provider, params QueryablePlan[] plans) + { + if (provider == null) + { + throw new ArgumentNullException(nameof(provider)); + } + + if (plans == null) + { + throw new ArgumentNullException(nameof(plans)); + } + + return provider.CreateQuery( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodBase.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), +#pragma warning restore IL2060 + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.NewArrayInit( + typeof(QueryablePlan), + plans.Select(p => p.Expression) + ) + ) + ); + } + + /// + /// Joins together the results from several patterns. + /// + /// The type of the elements in the result sequence, obtained from the specified patterns. + /// Query provider used to construct the data source. + /// A series of plans created by use of the Then operator on patterns. + /// An observable sequence with the results form matching several patterns. + /// or is null. + public static IQbservable When(this IQbservableProvider provider, IEnumerable> plans) + { + if (provider == null) + { + throw new ArgumentNullException(nameof(provider)); + } + + if (plans == null) + { + throw new ArgumentNullException(nameof(plans)); + } + + return provider.CreateQuery( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodBase.GetCurrentMethod()!).MakeGenericMethod(typeof(TResult)), +#pragma warning restore IL2060 + Expression.Constant(provider, typeof(IQbservableProvider)), + Expression.Constant(plans, typeof(IEnumerable>)) + ) + ); + } + } +} diff --git a/LibExternal/System.Reactive/Linq/Qbservable.cs b/LibExternal/System.Reactive/Linq/Qbservable.cs new file mode 100644 index 0000000..17e51b1 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/Qbservable.cs @@ -0,0 +1,136 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reactive.Concurrency; +using System.Reflection; + +namespace System.Reactive.Linq +{ + /// + /// Provides a set of static methods for writing queries over observable sequences, allowing translation to a target query language. + /// + public static partial class Qbservable + { + /// + /// Returns the input typed as an . + /// This operator is used to separate the part of the query that's captured as an expression tree from the part that's executed locally. + /// + /// The type of the elements in the source sequence. + /// An sequence to convert to an sequence. + /// The original source object, but typed as an . + /// is null. + public static IObservable AsObservable(this IQbservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return source; + } + + /// + /// Converts an enumerable sequence to an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Enumerable sequence to convert to an observable sequence. + /// The observable sequence whose elements are pulled from the given enumerable sequence. + /// is null. + /// This operator requires the source's object (see ) to implement . + public static IQbservable ToQbservable(this IQueryable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return ((IQbservableProvider)source.Provider).CreateQuery( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodBase.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), +#pragma warning restore IL2060 + source.Expression + ) + ); + } + + /// + /// Converts an enumerable sequence to an observable sequence, using the specified scheduler to run the enumeration loop. + /// + /// The type of the elements in the source sequence. + /// Enumerable sequence to convert to an observable sequence. + /// Scheduler to run the enumeration of the input sequence on. + /// The observable sequence whose elements are pulled from the given enumerable sequence. + /// or is null. + /// This operator requires the source's object (see ) to implement . + public static IQbservable ToQbservable(this IQueryable source, IScheduler scheduler) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return ((IQbservableProvider)source.Provider).CreateQuery( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodBase.GetCurrentMethod()!).MakeGenericMethod(typeof(TSource)), +#pragma warning restore IL2060 + source.Expression, + Expression.Constant(scheduler) + ) + ); + } + + internal static Expression GetSourceExpression(IObservable source) + { + if (source is IQbservable q) + { + return q.Expression; + } + + return Expression.Constant(source, typeof(IObservable)); + } + + internal static Expression GetSourceExpression(IEnumerable source) + { + if (source is IQueryable q) + { + return q.Expression; + } + + return Expression.Constant(source, typeof(IEnumerable)); + } + + internal static Expression GetSourceExpression(IObservable[] sources) + { + return Expression.NewArrayInit( + typeof(IObservable), + sources.Select(source => GetSourceExpression(source)) + ); + } + + internal static Expression GetSourceExpression(IEnumerable[] sources) + { + return Expression.NewArrayInit( + typeof(IEnumerable), + sources.Select(source => GetSourceExpression(source)) + ); + } + + internal static MethodInfo InfoOf(Expression> f) + { + return ((MethodCallExpression)f.Body).Method; + } + } +} diff --git a/LibExternal/System.Reactive/Linq/QbservableEx.Generated.cs b/LibExternal/System.Reactive/Linq/QbservableEx.Generated.cs new file mode 100644 index 0000000..7696de1 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QbservableEx.Generated.cs @@ -0,0 +1,365 @@ +/* + * WARNING: Auto-generated file (05/28/2018 22:20:19) + * Run Rx's auto-homoiconizer tool to generate this file (in the HomoIcon directory). + */ + +#pragma warning disable 1591 + +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reactive.Concurrency; +using System.Reflection; + +namespace System.Reactive.Linq +{ + public static partial class QbservableEx + { +#if !STABLE + /// + /// Subscribes to each observable sequence returned by the iteratorMethod in sequence and produces a Unit value on the resulting sequence for each step of the iteration. + /// + /// Query provider used to construct the data source. + /// Iterator method that drives the resulting observable sequence. + /// An observable sequence obtained by running the iterator and returning Unit values for each iteration step. + /// + /// is null. + [Experimental] + public static IQbservable Create(this IQbservableProvider provider, Expression>>> iteratorMethod) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (iteratorMethod == null) + throw new ArgumentNullException(nameof(iteratorMethod)); + + return provider.CreateQuery( + Expression.Call( + null, + (MethodInfo)MethodInfo.GetCurrentMethod(), + Expression.Constant(provider, typeof(IQbservableProvider)), + iteratorMethod + ) + ); + } +#endif + +#if !STABLE + /// + /// Subscribes to each observable sequence returned by the iteratorMethod in sequence and returns the observable sequence of values sent to the observer given to the iteratorMethod. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the produced sequence. + /// Iterator method that produces elements in the resulting sequence by calling the given observer. + /// An observable sequence obtained by running the iterator and returning the elements that were sent to the observer. + /// + /// is null. + [Experimental] + public static IQbservable Create(this IQbservableProvider provider, Expression, IEnumerable>>> iteratorMethod) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (iteratorMethod == null) + throw new ArgumentNullException(nameof(iteratorMethod)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()).MakeGenericMethod(typeof(TResult)), + Expression.Constant(provider, typeof(IQbservableProvider)), + iteratorMethod + ) + ); + } +#endif + +#if !STABLE + /// + /// Expands an observable sequence by recursively invoking selector. + /// + /// The type of the elements in the source sequence and each of the recursively expanded sources obtained by running the selector function. + /// Source sequence with the initial elements. + /// Selector function to invoke for each produced element, resulting in another sequence to which the selector will be invoked recursively again. + /// An observable sequence containing all the elements produced by the recursive expansion. + /// + /// or is null. + [Experimental] + public static IQbservable Expand(this IQbservable source, Expression>> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector + ) + ); + } +#endif + +#if !STABLE + /// + /// Expands an observable sequence by recursively invoking selector, using the specified scheduler to enumerate the queue of obtained sequences. + /// + /// The type of the elements in the source sequence and each of the recursively expanded sources obtained by running the selector function. + /// Source sequence with the initial elements. + /// Selector function to invoke for each produced element, resulting in another sequence to which the selector will be invoked recursively again. + /// Scheduler on which to perform the expansion by enumerating the internal queue of obtained sequences. + /// An observable sequence containing all the elements produced by the recursive expansion. + /// + /// or or is null. + [Experimental] + public static IQbservable Expand(this IQbservable source, Expression>> selector, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()).MakeGenericMethod(typeof(TSource)), + source.Expression, + selector, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } +#endif + +#if !STABLE + /// + /// Runs all specified observable sequences in parallel and collects their last elements. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequences. + /// Observable sequence to collect the last elements for. + /// An observable sequence with an array collecting the last elements of all the input sequences. + /// + /// is null. + [Experimental] + public static IQbservable ForkJoin(this IQbservableProvider provider, params IObservable[] sources) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + GetSourceExpression(sources) + ) + ); + } +#endif + +#if !STABLE + /// + /// Runs all observable sequences in the enumerable sources sequence in parallel and collect their last elements. + /// + /// Query provider used to construct the data source. + /// The type of the elements in the source sequences. + /// Observable sequence to collect the last elements for. + /// An observable sequence with an array collecting the last elements of all the input sequences. + /// + /// is null. + [Experimental] + public static IQbservable ForkJoin(this IQbservableProvider provider, IEnumerable> sources) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + if (sources == null) + throw new ArgumentNullException(nameof(sources)); + + return provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()).MakeGenericMethod(typeof(TSource)), + Expression.Constant(provider, typeof(IQbservableProvider)), + GetSourceExpression(sources) + ) + ); + } +#endif + +#if !STABLE + /// + /// Runs two observable sequences in parallel and combines their last elements. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the result sequence, returned by the selector function. + /// First observable sequence. + /// Second observable sequence. + /// Result selector function to invoke with the last elements of both sequences. + /// An observable sequence with the result of calling the selector function with the last elements of both input sequences. + /// + /// or or is null. + [Experimental] + public static IQbservable ForkJoin(this IQbservable first, IObservable second, Expression> resultSelector) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (resultSelector == null) + throw new ArgumentNullException(nameof(resultSelector)); + + return first.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()).MakeGenericMethod(typeof(TSource1), typeof(TSource2), typeof(TResult)), + first.Expression, + GetSourceExpression(second), + resultSelector + ) + ); + } +#endif + +#if !STABLE + /// + /// Returns an observable sequence that is the result of invoking the selector on the source sequence, without sharing subscriptions. + /// This operator allows for a fluent style of writing queries that use the same sequence multiple times. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the result sequence. + /// Source sequence that will be shared in the selector function. + /// Selector function which can use the source sequence as many times as needed, without sharing subscriptions to the source sequence. + /// An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. + /// + /// or is null. + [Experimental] + public static IQbservable Let(this IQbservable source, Expression, IObservable>> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector + ) + ); + } +#endif + +#if !STABLE + /// + /// Comonadic bind operator. + /// + [Experimental] + public static IQbservable ManySelect(this IQbservable source, Expression, TResult>> selector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector + ) + ); + } +#endif + +#if !STABLE + /// + /// Comonadic bind operator. + /// + [Experimental] + public static IQbservable ManySelect(this IQbservable source, Expression, TResult>> selector, IScheduler scheduler) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + if (scheduler == null) + throw new ArgumentNullException(nameof(scheduler)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()).MakeGenericMethod(typeof(TSource), typeof(TResult)), + source.Expression, + selector, + Expression.Constant(scheduler, typeof(IScheduler)) + ) + ); + } +#endif + + /// + /// Merges two observable sequences into one observable sequence by combining each element from the first source with the latest element from the second source, if any. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// First observable source. + /// Second observable source. + /// An observable sequence containing the result of combining each element of the first source with the latest element from the second source, if any, as a tuple value. + /// or is null. + public static IQbservable<(TFirst First, TSecond Second)> WithLatestFrom(this IQbservable first, IObservable second) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second)>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()).MakeGenericMethod(typeof(TFirst), typeof(TSecond)), + first.Expression, + GetSourceExpression(second) + ) + ); + } + + /// + /// Merges an observable sequence and an enumerable sequence into one observable sequence of tuple values. + /// + /// The type of the elements in the first observable source sequence. + /// The type of the elements in the second enumerable source sequence. + /// First observable source. + /// Second enumerable source. + /// An observable sequence containing the result of pairwise combining the elements of the first and second source as a tuple value. + /// or is null. + public static IQbservable<(TFirst First, TSecond Second)> Zip(this IQbservable first, IEnumerable second) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second)>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()).MakeGenericMethod(typeof(TFirst), typeof(TSecond)), + first.Expression, + GetSourceExpression(second) + ) + ); + } + + } +} + +#pragma warning restore 1591 + diff --git a/LibExternal/System.Reactive/Linq/QbservableEx.NAry.cs b/LibExternal/System.Reactive/Linq/QbservableEx.NAry.cs new file mode 100644 index 0000000..5df7527 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QbservableEx.NAry.cs @@ -0,0 +1,1907 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +// This code was generated by a T4 template at 04/19/2023 18:04:44. + +using System.Linq.Expressions; +using System.Reflection; + +namespace System.Reactive.Linq +{ +#pragma warning disable CA1711 // (Don't use Ex suffix.) This has been a public type for many years, so we can't rename it now. + public static partial class QbservableEx +#pragma warning restore CA1711 + { + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// First observable source. + /// Second observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or is null. + public static IQbservable<(TFirst First, TSecond Second)> CombineLatest(this IQbservable first, IObservable second) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond)), + first.Expression, + GetSourceExpression(second) + ) + ); +#pragma warning restore IL2060 // Call to 'System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed. It's not possible to guarantee the availability of requirements of the generic method. + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third)> CombineLatest(this IQbservable first, IObservable second, IObservable third) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth)> CombineLatest(this IQbservable first, IObservable second, IObservable third, IObservable fourth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth)> CombineLatest(this IQbservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth), + GetSourceExpression(fifth) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth)> CombineLatest(this IQbservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth), + GetSourceExpression(fifth), + GetSourceExpression(sixth) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh)> CombineLatest(this IQbservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + if (seventh == null) + throw new ArgumentNullException(nameof(seventh)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth), + GetSourceExpression(fifth), + GetSourceExpression(sixth), + GetSourceExpression(seventh) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth)> CombineLatest(this IQbservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + if (seventh == null) + throw new ArgumentNullException(nameof(seventh)); + if (eighth == null) + throw new ArgumentNullException(nameof(eighth)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh), typeof(TEighth)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth), + GetSourceExpression(fifth), + GetSourceExpression(sixth), + GetSourceExpression(seventh), + GetSourceExpression(eighth) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth)> CombineLatest(this IQbservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth, IObservable ninth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + if (seventh == null) + throw new ArgumentNullException(nameof(seventh)); + if (eighth == null) + throw new ArgumentNullException(nameof(eighth)); + if (ninth == null) + throw new ArgumentNullException(nameof(ninth)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh), typeof(TEighth), typeof(TNinth)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth), + GetSourceExpression(fifth), + GetSourceExpression(sixth), + GetSourceExpression(seventh), + GetSourceExpression(eighth), + GetSourceExpression(ninth) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or or or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth)> CombineLatest(this IQbservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth, IObservable ninth, IObservable tenth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + if (seventh == null) + throw new ArgumentNullException(nameof(seventh)); + if (eighth == null) + throw new ArgumentNullException(nameof(eighth)); + if (ninth == null) + throw new ArgumentNullException(nameof(ninth)); + if (tenth == null) + throw new ArgumentNullException(nameof(tenth)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh), typeof(TEighth), typeof(TNinth), typeof(TTenth)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth), + GetSourceExpression(fifth), + GetSourceExpression(sixth), + GetSourceExpression(seventh), + GetSourceExpression(eighth), + GetSourceExpression(ninth), + GetSourceExpression(tenth) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or or or or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth, TEleventh Eleventh)> CombineLatest(this IQbservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth, IObservable ninth, IObservable tenth, IObservable eleventh) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + if (seventh == null) + throw new ArgumentNullException(nameof(seventh)); + if (eighth == null) + throw new ArgumentNullException(nameof(eighth)); + if (ninth == null) + throw new ArgumentNullException(nameof(ninth)); + if (tenth == null) + throw new ArgumentNullException(nameof(tenth)); + if (eleventh == null) + throw new ArgumentNullException(nameof(eleventh)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth, TEleventh Eleventh)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh), typeof(TEighth), typeof(TNinth), typeof(TTenth), typeof(TEleventh)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth), + GetSourceExpression(fifth), + GetSourceExpression(sixth), + GetSourceExpression(seventh), + GetSourceExpression(eighth), + GetSourceExpression(ninth), + GetSourceExpression(tenth), + GetSourceExpression(eleventh) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or or or or or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth, TEleventh Eleventh, TTwelfth Twelfth)> CombineLatest(this IQbservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth, IObservable ninth, IObservable tenth, IObservable eleventh, IObservable twelfth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + if (seventh == null) + throw new ArgumentNullException(nameof(seventh)); + if (eighth == null) + throw new ArgumentNullException(nameof(eighth)); + if (ninth == null) + throw new ArgumentNullException(nameof(ninth)); + if (tenth == null) + throw new ArgumentNullException(nameof(tenth)); + if (eleventh == null) + throw new ArgumentNullException(nameof(eleventh)); + if (twelfth == null) + throw new ArgumentNullException(nameof(twelfth)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth, TEleventh Eleventh, TTwelfth Twelfth)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh), typeof(TEighth), typeof(TNinth), typeof(TTenth), typeof(TEleventh), typeof(TTwelfth)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth), + GetSourceExpression(fifth), + GetSourceExpression(sixth), + GetSourceExpression(seventh), + GetSourceExpression(eighth), + GetSourceExpression(ninth), + GetSourceExpression(tenth), + GetSourceExpression(eleventh), + GetSourceExpression(twelfth) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Thirteenth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or or or or or or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth, TEleventh Eleventh, TTwelfth Twelfth, TThirteenth Thirteenth)> CombineLatest(this IQbservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth, IObservable ninth, IObservable tenth, IObservable eleventh, IObservable twelfth, IObservable thirteenth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + if (seventh == null) + throw new ArgumentNullException(nameof(seventh)); + if (eighth == null) + throw new ArgumentNullException(nameof(eighth)); + if (ninth == null) + throw new ArgumentNullException(nameof(ninth)); + if (tenth == null) + throw new ArgumentNullException(nameof(tenth)); + if (eleventh == null) + throw new ArgumentNullException(nameof(eleventh)); + if (twelfth == null) + throw new ArgumentNullException(nameof(twelfth)); + if (thirteenth == null) + throw new ArgumentNullException(nameof(thirteenth)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth, TEleventh Eleventh, TTwelfth Twelfth, TThirteenth Thirteenth)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh), typeof(TEighth), typeof(TNinth), typeof(TTenth), typeof(TEleventh), typeof(TTwelfth), typeof(TThirteenth)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth), + GetSourceExpression(fifth), + GetSourceExpression(sixth), + GetSourceExpression(seventh), + GetSourceExpression(eighth), + GetSourceExpression(ninth), + GetSourceExpression(tenth), + GetSourceExpression(eleventh), + GetSourceExpression(twelfth), + GetSourceExpression(thirteenth) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the fourteenth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Thirteenth observable source. + /// Fourteenth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or or or or or or or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth, TEleventh Eleventh, TTwelfth Twelfth, TThirteenth Thirteenth, TFourteenth Fourteenth)> CombineLatest(this IQbservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth, IObservable ninth, IObservable tenth, IObservable eleventh, IObservable twelfth, IObservable thirteenth, IObservable fourteenth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + if (seventh == null) + throw new ArgumentNullException(nameof(seventh)); + if (eighth == null) + throw new ArgumentNullException(nameof(eighth)); + if (ninth == null) + throw new ArgumentNullException(nameof(ninth)); + if (tenth == null) + throw new ArgumentNullException(nameof(tenth)); + if (eleventh == null) + throw new ArgumentNullException(nameof(eleventh)); + if (twelfth == null) + throw new ArgumentNullException(nameof(twelfth)); + if (thirteenth == null) + throw new ArgumentNullException(nameof(thirteenth)); + if (fourteenth == null) + throw new ArgumentNullException(nameof(fourteenth)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth, TEleventh Eleventh, TTwelfth Twelfth, TThirteenth Thirteenth, TFourteenth Fourteenth)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh), typeof(TEighth), typeof(TNinth), typeof(TTenth), typeof(TEleventh), typeof(TTwelfth), typeof(TThirteenth), typeof(TFourteenth)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth), + GetSourceExpression(fifth), + GetSourceExpression(sixth), + GetSourceExpression(seventh), + GetSourceExpression(eighth), + GetSourceExpression(ninth), + GetSourceExpression(tenth), + GetSourceExpression(eleventh), + GetSourceExpression(twelfth), + GetSourceExpression(thirteenth), + GetSourceExpression(fourteenth) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the fourteenth source sequence. + /// The type of the elements in the fifteenth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Thirteenth observable source. + /// Fourteenth observable source. + /// Fifteenth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or or or or or or or or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth, TEleventh Eleventh, TTwelfth Twelfth, TThirteenth Thirteenth, TFourteenth Fourteenth, TFifteenth Fifteenth)> CombineLatest(this IQbservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth, IObservable ninth, IObservable tenth, IObservable eleventh, IObservable twelfth, IObservable thirteenth, IObservable fourteenth, IObservable fifteenth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + if (seventh == null) + throw new ArgumentNullException(nameof(seventh)); + if (eighth == null) + throw new ArgumentNullException(nameof(eighth)); + if (ninth == null) + throw new ArgumentNullException(nameof(ninth)); + if (tenth == null) + throw new ArgumentNullException(nameof(tenth)); + if (eleventh == null) + throw new ArgumentNullException(nameof(eleventh)); + if (twelfth == null) + throw new ArgumentNullException(nameof(twelfth)); + if (thirteenth == null) + throw new ArgumentNullException(nameof(thirteenth)); + if (fourteenth == null) + throw new ArgumentNullException(nameof(fourteenth)); + if (fifteenth == null) + throw new ArgumentNullException(nameof(fifteenth)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth, TEleventh Eleventh, TTwelfth Twelfth, TThirteenth Thirteenth, TFourteenth Fourteenth, TFifteenth Fifteenth)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh), typeof(TEighth), typeof(TNinth), typeof(TTenth), typeof(TEleventh), typeof(TTwelfth), typeof(TThirteenth), typeof(TFourteenth), typeof(TFifteenth)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth), + GetSourceExpression(fifth), + GetSourceExpression(sixth), + GetSourceExpression(seventh), + GetSourceExpression(eighth), + GetSourceExpression(ninth), + GetSourceExpression(tenth), + GetSourceExpression(eleventh), + GetSourceExpression(twelfth), + GetSourceExpression(thirteenth), + GetSourceExpression(fourteenth), + GetSourceExpression(fifteenth) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever any of the observable sequences produces an element. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the fourteenth source sequence. + /// The type of the elements in the fifteenth source sequence. + /// The type of the elements in the sixteenth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Thirteenth observable source. + /// Fourteenth observable source. + /// Fifteenth observable source. + /// Sixteenth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or or or or or or or or or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth, TEleventh Eleventh, TTwelfth Twelfth, TThirteenth Thirteenth, TFourteenth Fourteenth, TFifteenth Fifteenth, TSixteenth Sixteenth)> CombineLatest(this IQbservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth, IObservable ninth, IObservable tenth, IObservable eleventh, IObservable twelfth, IObservable thirteenth, IObservable fourteenth, IObservable fifteenth, IObservable sixteenth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + if (seventh == null) + throw new ArgumentNullException(nameof(seventh)); + if (eighth == null) + throw new ArgumentNullException(nameof(eighth)); + if (ninth == null) + throw new ArgumentNullException(nameof(ninth)); + if (tenth == null) + throw new ArgumentNullException(nameof(tenth)); + if (eleventh == null) + throw new ArgumentNullException(nameof(eleventh)); + if (twelfth == null) + throw new ArgumentNullException(nameof(twelfth)); + if (thirteenth == null) + throw new ArgumentNullException(nameof(thirteenth)); + if (fourteenth == null) + throw new ArgumentNullException(nameof(fourteenth)); + if (fifteenth == null) + throw new ArgumentNullException(nameof(fifteenth)); + if (sixteenth == null) + throw new ArgumentNullException(nameof(sixteenth)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth, TEleventh Eleventh, TTwelfth Twelfth, TThirteenth Thirteenth, TFourteenth Fourteenth, TFifteenth Fifteenth, TSixteenth Sixteenth)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh), typeof(TEighth), typeof(TNinth), typeof(TTenth), typeof(TEleventh), typeof(TTwelfth), typeof(TThirteenth), typeof(TFourteenth), typeof(TFifteenth), typeof(TSixteenth)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth), + GetSourceExpression(fifth), + GetSourceExpression(sixth), + GetSourceExpression(seventh), + GetSourceExpression(eighth), + GetSourceExpression(ninth), + GetSourceExpression(tenth), + GetSourceExpression(eleventh), + GetSourceExpression(twelfth), + GetSourceExpression(thirteenth), + GetSourceExpression(fourteenth), + GetSourceExpression(fifteenth), + GetSourceExpression(sixteenth) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// First observable source. + /// Second observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or is null. + public static IQbservable<(TFirst First, TSecond Second)> Zip(this IQbservable first, IObservable second) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third)> Zip(this IQbservable first, IObservable second, IObservable third) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth)> Zip(this IQbservable first, IObservable second, IObservable third, IObservable fourth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth)> Zip(this IQbservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth), + GetSourceExpression(fifth) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth)> Zip(this IQbservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth), + GetSourceExpression(fifth), + GetSourceExpression(sixth) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh)> Zip(this IQbservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + if (seventh == null) + throw new ArgumentNullException(nameof(seventh)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth), + GetSourceExpression(fifth), + GetSourceExpression(sixth), + GetSourceExpression(seventh) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth)> Zip(this IQbservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + if (seventh == null) + throw new ArgumentNullException(nameof(seventh)); + if (eighth == null) + throw new ArgumentNullException(nameof(eighth)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh), typeof(TEighth)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth), + GetSourceExpression(fifth), + GetSourceExpression(sixth), + GetSourceExpression(seventh), + GetSourceExpression(eighth) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth)> Zip(this IQbservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth, IObservable ninth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + if (seventh == null) + throw new ArgumentNullException(nameof(seventh)); + if (eighth == null) + throw new ArgumentNullException(nameof(eighth)); + if (ninth == null) + throw new ArgumentNullException(nameof(ninth)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh), typeof(TEighth), typeof(TNinth)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth), + GetSourceExpression(fifth), + GetSourceExpression(sixth), + GetSourceExpression(seventh), + GetSourceExpression(eighth), + GetSourceExpression(ninth) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or or or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth)> Zip(this IQbservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth, IObservable ninth, IObservable tenth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + if (seventh == null) + throw new ArgumentNullException(nameof(seventh)); + if (eighth == null) + throw new ArgumentNullException(nameof(eighth)); + if (ninth == null) + throw new ArgumentNullException(nameof(ninth)); + if (tenth == null) + throw new ArgumentNullException(nameof(tenth)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh), typeof(TEighth), typeof(TNinth), typeof(TTenth)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth), + GetSourceExpression(fifth), + GetSourceExpression(sixth), + GetSourceExpression(seventh), + GetSourceExpression(eighth), + GetSourceExpression(ninth), + GetSourceExpression(tenth) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or or or or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth, TEleventh Eleventh)> Zip(this IQbservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth, IObservable ninth, IObservable tenth, IObservable eleventh) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + if (seventh == null) + throw new ArgumentNullException(nameof(seventh)); + if (eighth == null) + throw new ArgumentNullException(nameof(eighth)); + if (ninth == null) + throw new ArgumentNullException(nameof(ninth)); + if (tenth == null) + throw new ArgumentNullException(nameof(tenth)); + if (eleventh == null) + throw new ArgumentNullException(nameof(eleventh)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth, TEleventh Eleventh)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh), typeof(TEighth), typeof(TNinth), typeof(TTenth), typeof(TEleventh)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth), + GetSourceExpression(fifth), + GetSourceExpression(sixth), + GetSourceExpression(seventh), + GetSourceExpression(eighth), + GetSourceExpression(ninth), + GetSourceExpression(tenth), + GetSourceExpression(eleventh) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or or or or or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth, TEleventh Eleventh, TTwelfth Twelfth)> Zip(this IQbservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth, IObservable ninth, IObservable tenth, IObservable eleventh, IObservable twelfth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + if (seventh == null) + throw new ArgumentNullException(nameof(seventh)); + if (eighth == null) + throw new ArgumentNullException(nameof(eighth)); + if (ninth == null) + throw new ArgumentNullException(nameof(ninth)); + if (tenth == null) + throw new ArgumentNullException(nameof(tenth)); + if (eleventh == null) + throw new ArgumentNullException(nameof(eleventh)); + if (twelfth == null) + throw new ArgumentNullException(nameof(twelfth)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth, TEleventh Eleventh, TTwelfth Twelfth)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh), typeof(TEighth), typeof(TNinth), typeof(TTenth), typeof(TEleventh), typeof(TTwelfth)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth), + GetSourceExpression(fifth), + GetSourceExpression(sixth), + GetSourceExpression(seventh), + GetSourceExpression(eighth), + GetSourceExpression(ninth), + GetSourceExpression(tenth), + GetSourceExpression(eleventh), + GetSourceExpression(twelfth) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Thirteenth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or or or or or or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth, TEleventh Eleventh, TTwelfth Twelfth, TThirteenth Thirteenth)> Zip(this IQbservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth, IObservable ninth, IObservable tenth, IObservable eleventh, IObservable twelfth, IObservable thirteenth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + if (seventh == null) + throw new ArgumentNullException(nameof(seventh)); + if (eighth == null) + throw new ArgumentNullException(nameof(eighth)); + if (ninth == null) + throw new ArgumentNullException(nameof(ninth)); + if (tenth == null) + throw new ArgumentNullException(nameof(tenth)); + if (eleventh == null) + throw new ArgumentNullException(nameof(eleventh)); + if (twelfth == null) + throw new ArgumentNullException(nameof(twelfth)); + if (thirteenth == null) + throw new ArgumentNullException(nameof(thirteenth)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth, TEleventh Eleventh, TTwelfth Twelfth, TThirteenth Thirteenth)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh), typeof(TEighth), typeof(TNinth), typeof(TTenth), typeof(TEleventh), typeof(TTwelfth), typeof(TThirteenth)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth), + GetSourceExpression(fifth), + GetSourceExpression(sixth), + GetSourceExpression(seventh), + GetSourceExpression(eighth), + GetSourceExpression(ninth), + GetSourceExpression(tenth), + GetSourceExpression(eleventh), + GetSourceExpression(twelfth), + GetSourceExpression(thirteenth) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the fourteenth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Thirteenth observable source. + /// Fourteenth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or or or or or or or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth, TEleventh Eleventh, TTwelfth Twelfth, TThirteenth Thirteenth, TFourteenth Fourteenth)> Zip(this IQbservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth, IObservable ninth, IObservable tenth, IObservable eleventh, IObservable twelfth, IObservable thirteenth, IObservable fourteenth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + if (seventh == null) + throw new ArgumentNullException(nameof(seventh)); + if (eighth == null) + throw new ArgumentNullException(nameof(eighth)); + if (ninth == null) + throw new ArgumentNullException(nameof(ninth)); + if (tenth == null) + throw new ArgumentNullException(nameof(tenth)); + if (eleventh == null) + throw new ArgumentNullException(nameof(eleventh)); + if (twelfth == null) + throw new ArgumentNullException(nameof(twelfth)); + if (thirteenth == null) + throw new ArgumentNullException(nameof(thirteenth)); + if (fourteenth == null) + throw new ArgumentNullException(nameof(fourteenth)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth, TEleventh Eleventh, TTwelfth Twelfth, TThirteenth Thirteenth, TFourteenth Fourteenth)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh), typeof(TEighth), typeof(TNinth), typeof(TTenth), typeof(TEleventh), typeof(TTwelfth), typeof(TThirteenth), typeof(TFourteenth)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth), + GetSourceExpression(fifth), + GetSourceExpression(sixth), + GetSourceExpression(seventh), + GetSourceExpression(eighth), + GetSourceExpression(ninth), + GetSourceExpression(tenth), + GetSourceExpression(eleventh), + GetSourceExpression(twelfth), + GetSourceExpression(thirteenth), + GetSourceExpression(fourteenth) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the fourteenth source sequence. + /// The type of the elements in the fifteenth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Thirteenth observable source. + /// Fourteenth observable source. + /// Fifteenth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or or or or or or or or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth, TEleventh Eleventh, TTwelfth Twelfth, TThirteenth Thirteenth, TFourteenth Fourteenth, TFifteenth Fifteenth)> Zip(this IQbservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth, IObservable ninth, IObservable tenth, IObservable eleventh, IObservable twelfth, IObservable thirteenth, IObservable fourteenth, IObservable fifteenth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + if (seventh == null) + throw new ArgumentNullException(nameof(seventh)); + if (eighth == null) + throw new ArgumentNullException(nameof(eighth)); + if (ninth == null) + throw new ArgumentNullException(nameof(ninth)); + if (tenth == null) + throw new ArgumentNullException(nameof(tenth)); + if (eleventh == null) + throw new ArgumentNullException(nameof(eleventh)); + if (twelfth == null) + throw new ArgumentNullException(nameof(twelfth)); + if (thirteenth == null) + throw new ArgumentNullException(nameof(thirteenth)); + if (fourteenth == null) + throw new ArgumentNullException(nameof(fourteenth)); + if (fifteenth == null) + throw new ArgumentNullException(nameof(fifteenth)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth, TEleventh Eleventh, TTwelfth Twelfth, TThirteenth Thirteenth, TFourteenth Fourteenth, TFifteenth Fifteenth)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh), typeof(TEighth), typeof(TNinth), typeof(TTenth), typeof(TEleventh), typeof(TTwelfth), typeof(TThirteenth), typeof(TFourteenth), typeof(TFifteenth)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth), + GetSourceExpression(fifth), + GetSourceExpression(sixth), + GetSourceExpression(seventh), + GetSourceExpression(eighth), + GetSourceExpression(ninth), + GetSourceExpression(tenth), + GetSourceExpression(eleventh), + GetSourceExpression(twelfth), + GetSourceExpression(thirteenth), + GetSourceExpression(fourteenth), + GetSourceExpression(fifteenth) + ) + ); + } + + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever all of the observable sequences have produced an element at a corresponding index. + /// + /// The type of the elements in the first source sequence. + /// The type of the elements in the second source sequence. + /// The type of the elements in the third source sequence. + /// The type of the elements in the fourth source sequence. + /// The type of the elements in the fifth source sequence. + /// The type of the elements in the sixth source sequence. + /// The type of the elements in the seventh source sequence. + /// The type of the elements in the eighth source sequence. + /// The type of the elements in the ninth source sequence. + /// The type of the elements in the tenth source sequence. + /// The type of the elements in the eleventh source sequence. + /// The type of the elements in the twelfth source sequence. + /// The type of the elements in the thirteenth source sequence. + /// The type of the elements in the fourteenth source sequence. + /// The type of the elements in the fifteenth source sequence. + /// The type of the elements in the sixteenth source sequence. + /// First observable source. + /// Second observable source. + /// Third observable source. + /// Fourth observable source. + /// Fifth observable source. + /// Sixth observable source. + /// Seventh observable source. + /// Eighth observable source. + /// Ninth observable source. + /// Tenth observable source. + /// Eleventh observable source. + /// Twelfth observable source. + /// Thirteenth observable source. + /// Fourteenth observable source. + /// Fifteenth observable source. + /// Sixteenth observable source. + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// or or or or or or or or or or or or or or or is null. + public static IQbservable<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth, TEleventh Eleventh, TTwelfth Twelfth, TThirteenth Thirteenth, TFourteenth Fourteenth, TFifteenth Fifteenth, TSixteenth Sixteenth)> Zip(this IQbservable first, IObservable second, IObservable third, IObservable fourth, IObservable fifth, IObservable sixth, IObservable seventh, IObservable eighth, IObservable ninth, IObservable tenth, IObservable eleventh, IObservable twelfth, IObservable thirteenth, IObservable fourteenth, IObservable fifteenth, IObservable sixteenth) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + if (third == null) + throw new ArgumentNullException(nameof(third)); + if (fourth == null) + throw new ArgumentNullException(nameof(fourth)); + if (fifth == null) + throw new ArgumentNullException(nameof(fifth)); + if (sixth == null) + throw new ArgumentNullException(nameof(sixth)); + if (seventh == null) + throw new ArgumentNullException(nameof(seventh)); + if (eighth == null) + throw new ArgumentNullException(nameof(eighth)); + if (ninth == null) + throw new ArgumentNullException(nameof(ninth)); + if (tenth == null) + throw new ArgumentNullException(nameof(tenth)); + if (eleventh == null) + throw new ArgumentNullException(nameof(eleventh)); + if (twelfth == null) + throw new ArgumentNullException(nameof(twelfth)); + if (thirteenth == null) + throw new ArgumentNullException(nameof(thirteenth)); + if (fourteenth == null) + throw new ArgumentNullException(nameof(fourteenth)); + if (fifteenth == null) + throw new ArgumentNullException(nameof(fifteenth)); + if (sixteenth == null) + throw new ArgumentNullException(nameof(sixteenth)); + + return first.Provider.CreateQuery<(TFirst First, TSecond Second, TThird Third, TFourth Fourth, TFifth Fifth, TSixth Sixth, TSeventh Seventh, TEighth Eighth, TNinth Ninth, TTenth Tenth, TEleventh Eleventh, TTwelfth Twelfth, TThirteenth Thirteenth, TFourteenth Fourteenth, TFifteenth Fifteenth, TSixteenth Sixteenth)>( + Expression.Call( + null, +#pragma warning disable IL2060 // ('System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.) This gets the MethodInfo for the method running right now, so it can't have been trimmed + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh), typeof(TEighth), typeof(TNinth), typeof(TTenth), typeof(TEleventh), typeof(TTwelfth), typeof(TThirteenth), typeof(TFourteenth), typeof(TFifteenth), typeof(TSixteenth)), +#pragma warning restore IL2060 + first.Expression, + GetSourceExpression(second), + GetSourceExpression(third), + GetSourceExpression(fourth), + GetSourceExpression(fifth), + GetSourceExpression(sixth), + GetSourceExpression(seventh), + GetSourceExpression(eighth), + GetSourceExpression(ninth), + GetSourceExpression(tenth), + GetSourceExpression(eleventh), + GetSourceExpression(twelfth), + GetSourceExpression(thirteenth), + GetSourceExpression(fourteenth), + GetSourceExpression(fifteenth), + GetSourceExpression(sixteenth) + ) + ); + } + + } +} diff --git a/LibExternal/System.Reactive/Linq/QbservableEx.NAry.tt b/LibExternal/System.Reactive/Linq/QbservableEx.NAry.tt new file mode 100644 index 0000000..4f0453f --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QbservableEx.NAry.tt @@ -0,0 +1,154 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> +// This code was generated by a T4 template at <#=DateTime.Now#>. + +using System.Linq.Expressions; +using System.Reflection; + +namespace System.Reactive.Linq +{ + public static partial class QbservableEx + { +<# +Func toUpper = s => char.ToUpper(s[0]) + s.Substring(1); + +string[] ordinals = new[] { "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth", "tenth", "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", "sixteenth" }; + +for (int i = 2; i <= 16; i++) +{ + var parameters = string.Join(", ", Enumerable.Range(1, i).Select(j => "I" + (j == 1 ? "Q" : "O") + "bservable " + ordinals[j - 1])); + var genArgs = string.Join(", ", Enumerable.Range(1, i).Select(j => "T" + toUpper(ordinals[j - 1]))); + var sources = string.Join(", ", Enumerable.Range(1, i).Select(j => ordinals[j - 1])); + var paramRefs = string.Join(" or ", Enumerable.Range(1, i).Select(j => "")); + var tuple = "(" + string.Join(", ", Enumerable.Range(1, i).Select(j => "T" + toUpper(ordinals[j - 1]) + " " + toUpper(ordinals[j - 1]))) + ")"; + var vals = string.Join(", ", Enumerable.Range(1, i).Select(j => "t" + j)); + var typeofGenArgs = string.Join(", ", Enumerable.Range(1, i).Select(j => "typeof(T" + toUpper(ordinals[j - 1]) + ")")); + +#> + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever any of the observable sequences produces an element. + /// +<# +for (int j = 0; j < i; j++) +{ +#> + /// The type of the elements in the <#=ordinals[j]#> source sequence. +<# +} +#> +<# +for (int j = 0; j < i; j++) +{ +#> + /// <#=toUpper(ordinals[j])#> observable source. +<# +} +#> + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// <#=paramRefs#> is null. + public static IQbservable<<#=tuple#>> CombineLatest<<#=genArgs#>>(this <#=parameters#>) + { +<# +for (int j = 1; j <= i; j++) +{ +#> + if (<#=ordinals[j - 1]#> == null) + throw new ArgumentNullException(nameof(<#=ordinals[j - 1]#>)); +<# +} +#> + + return first.Provider.CreateQuery<<#=tuple#>>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(<#=typeofGenArgs#>), + first.Expression, +<# +for (int j = 2; j <= i; j++) +{ +#> + GetSourceExpression(<#=ordinals[j - 1]#>)<#=j != i ? "," : ""#> +<# +} +#> + ) + ); + } + +<# +} + +for (int i = 2; i <= 16; i++) +{ + var parameters = string.Join(", ", Enumerable.Range(1, i).Select(j => "I" + (j == 1 ? "Q" : "O") + "bservable " + ordinals[j - 1])); + var genArgs = string.Join(", ", Enumerable.Range(1, i).Select(j => "T" + toUpper(ordinals[j - 1]))); + var sources = string.Join(", ", Enumerable.Range(1, i).Select(j => ordinals[j - 1])); + var paramRefs = string.Join(" or ", Enumerable.Range(1, i).Select(j => "")); + var tuple = "(" + string.Join(", ", Enumerable.Range(1, i).Select(j => "T" + toUpper(ordinals[j - 1]) + " " + toUpper(ordinals[j - 1]))) + ")"; + var vals = string.Join(", ", Enumerable.Range(1, i).Select(j => "t" + j)); + var typeofGenArgs = string.Join(", ", Enumerable.Range(1, i).Select(j => "typeof(T" + toUpper(ordinals[j - 1]) + ")")); + +#> + /// + /// Merges the specified observable sequences into one observable sequence of tuple values whenever all of the observable sequences have produced an element at a corresponding index. + /// +<# +for (int j = 0; j < i; j++) +{ +#> + /// The type of the elements in the <#=ordinals[j]#> source sequence. +<# +} +#> +<# +for (int j = 0; j < i; j++) +{ +#> + /// <#=toUpper(ordinals[j])#> observable source. +<# +} +#> + /// An observable sequence containing the result of combining elements of the sources using tuple values. + /// <#=paramRefs#> is null. + public static IQbservable<<#=tuple#>> Zip<<#=genArgs#>>(this <#=parameters#>) + { +<# +for (int j = 1; j <= i; j++) +{ +#> + if (<#=ordinals[j - 1]#> == null) + throw new ArgumentNullException(nameof(<#=ordinals[j - 1]#>)); +<# +} +#> + + return first.Provider.CreateQuery<<#=tuple#>>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()!).MakeGenericMethod(<#=typeofGenArgs#>), + first.Expression, +<# +for (int j = 2; j <= i; j++) +{ +#> + GetSourceExpression(<#=ordinals[j - 1]#>)<#=j != i ? "," : ""#> +<# +} +#> + ) + ); + } + +<# +} +#> + } +} diff --git a/LibExternal/System.Reactive/Linq/QbservableEx.cs b/LibExternal/System.Reactive/Linq/QbservableEx.cs new file mode 100644 index 0000000..87319ff --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QbservableEx.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +namespace System.Reactive.Linq +{ + /// + /// Provides a set of static methods for writing queries over observable sequences, allowing translation to a target query language. + /// + [LocalQueryMethodImplementationType(typeof(ObservableEx))] +#pragma warning disable CA1711 // (Don't use Ex suffix.) This has been a public type for many years, so we can't rename it now. + public static partial class QbservableEx +#pragma warning restore CA1711 + { + internal static Expression GetSourceExpression(IObservable source) + { + if (source is IQbservable q) + { + return q.Expression; + } + + return Expression.Constant(source, typeof(IObservable)); + } + + internal static Expression GetSourceExpression(IEnumerable source) + { + if (source is IQueryable q) + { + return q.Expression; + } + + return Expression.Constant(source, typeof(IEnumerable)); + } + + internal static Expression GetSourceExpression(IObservable[] sources) + { + return Expression.NewArrayInit( + typeof(IObservable), + sources.Select(source => GetSourceExpression(source)) + ); + } + + internal static Expression GetSourceExpression(IEnumerable[] sources) + { + return Expression.NewArrayInit( + typeof(IEnumerable), + sources.Select(source => GetSourceExpression(source)) + ); + } + + internal static MethodInfo InfoOf(Expression> f) + { + return ((MethodCallExpression)f.Body).Method; + } + } +} diff --git a/LibExternal/System.Reactive/Linq/QueryDebugger.cs b/LibExternal/System.Reactive/Linq/QueryDebugger.cs new file mode 100644 index 0000000..32aa7c3 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QueryDebugger.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +// +// This file acts as a placeholder for future extension with a debugger service +// intercepting all Observable[Ex] query operators, instrumenting those with a +// set of debugger hooks. The code would boil down to a wrapper implementation +// of IQueryLanguage[Ex] providing instrumentation for query operators, which +// ultimately calls into the original "baseImpl" passed to the Extend method. +// +// Likely we want this code to be auto-generated based on certain patterns that +// occur frequently in query operators. Also, to ensure debugger and target are +// not going out of sync, we should properly version the interfaces and possibly +// perform a runtime check for the loaded assembly versions to ensure everything +// lines up correctly. +// + +namespace System.Reactive.Linq +{ + /// + /// (Infrastructure) Implement query debugger services. + /// + public class QueryDebugger : IQueryServices + { + T IQueryServices.Extend(T baseImpl) + { + return baseImpl; + } + } +} diff --git a/LibExternal/System.Reactive/Linq/QueryLanguage.Aggregates.cs b/LibExternal/System.Reactive/Linq/QueryLanguage.Aggregates.cs new file mode 100644 index 0000000..d5dfdf8 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QueryLanguage.Aggregates.cs @@ -0,0 +1,791 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; + +namespace System.Reactive.Linq +{ + using ObservableImpl; + + internal partial class QueryLanguage + { + #region + Aggregate + + + public virtual IObservable Aggregate(IObservable source, TAccumulate seed, Func accumulator) + { + return new Aggregate(source, seed, accumulator); + } + + public virtual IObservable Aggregate(IObservable source, TAccumulate seed, Func accumulator, Func resultSelector) + { + return new Aggregate(source, seed, accumulator, resultSelector); + } + + public virtual IObservable Aggregate(IObservable source, Func accumulator) + { + return new Aggregate(source, accumulator); + } + + public virtual IObservable Average(IObservable source, Func selector) + { + return Average(Select(source, selector)); + } + + public virtual IObservable Average(IObservable source, Func selector) + { + return Average(Select(source, selector)); + } + + public virtual IObservable Average(IObservable source, Func selector) + { + return Average(Select(source, selector)); + } + + public virtual IObservable Average(IObservable source, Func selector) + { + return Average(Select(source, selector)); + } + + public virtual IObservable Average(IObservable source, Func selector) + { + return Average(Select(source, selector)); + } + + public virtual IObservable Average(IObservable source, Func selector) + { + return Average(Select(source, selector)); + } + + public virtual IObservable Average(IObservable source, Func selector) + { + return Average(Select(source, selector)); + } + + public virtual IObservable Average(IObservable source, Func selector) + { + return Average(Select(source, selector)); + } + + public virtual IObservable Average(IObservable source, Func selector) + { + return Average(Select(source, selector)); + } + + public virtual IObservable Average(IObservable source, Func selector) + { + return Average(Select(source, selector)); + } + + #endregion + + #region + All + + + public virtual IObservable All(IObservable source, Func predicate) + { + return new All(source, predicate); + } + + #endregion + + #region + Any + + + public virtual IObservable Any(IObservable source) + { + return new Any.Count(source); + } + + public virtual IObservable Any(IObservable source, Func predicate) + { + return new Any.Predicate(source, predicate); + } + + #endregion + + #region + Average + + + public virtual IObservable Average(IObservable source) + { + return new AverageDouble(source); + } + + public virtual IObservable Average(IObservable source) + { + return new AverageSingle(source); + } + + public virtual IObservable Average(IObservable source) + { + return new AverageDecimal(source); + } + + public virtual IObservable Average(IObservable source) + { + return new AverageInt32(source); + } + + public virtual IObservable Average(IObservable source) + { + return new AverageInt64(source); + } + + public virtual IObservable Average(IObservable source) + { + return new AverageDoubleNullable(source); + } + + public virtual IObservable Average(IObservable source) + { + return new AverageSingleNullable(source); + } + + public virtual IObservable Average(IObservable source) + { + return new AverageDecimalNullable(source); + } + + public virtual IObservable Average(IObservable source) + { + return new AverageInt32Nullable(source); + } + + public virtual IObservable Average(IObservable source) + { + return new AverageInt64Nullable(source); + } + + #endregion + + #region + Contains + + + public virtual IObservable Contains(IObservable source, TSource value) + { + return new Contains(source, value, EqualityComparer.Default); + } + + public virtual IObservable Contains(IObservable source, TSource value, IEqualityComparer comparer) + { + return new Contains(source, value, comparer); + } + + #endregion + + #region + Count + + + public virtual IObservable Count(IObservable source) + { + return new Count.All(source); + } + + public virtual IObservable Count(IObservable source, Func predicate) + { + return new Count.Predicate(source, predicate); + } + + #endregion + + #region + ElementAt + + + public virtual IObservable ElementAt(IObservable source, int index) + { + return new ElementAt(source, index); + } + + #endregion + + #region + ElementAtOrDefault + + + public virtual IObservable ElementAtOrDefault(IObservable source, int index) + { + return new ElementAtOrDefault(source, index); + } + + #endregion + + #region + FirstAsync + + + public virtual IObservable FirstAsync(IObservable source) + { + return new FirstAsync.Sequence(source); + } + + public virtual IObservable FirstAsync(IObservable source, Func predicate) + { + return new FirstAsync.Predicate(source, predicate); + } + + #endregion + + #region + FirstAsyncOrDefaultAsync + + + public virtual IObservable FirstOrDefaultAsync(IObservable source) + { + return new FirstOrDefaultAsync.Sequence(source); + } + + public virtual IObservable FirstOrDefaultAsync(IObservable source, Func predicate) + { + return new FirstOrDefaultAsync.Predicate(source, predicate); + } + + #endregion + + #region + IsEmpty + + + public virtual IObservable IsEmpty(IObservable source) + { + return new IsEmpty(source); + } + + #endregion + + #region + LastAsync + + + public virtual IObservable LastAsync(IObservable source) + { + return new LastAsync.Sequence(source); + } + + public virtual IObservable LastAsync(IObservable source, Func predicate) + { + return new LastAsync.Predicate(source, predicate); + } + + #endregion + + #region + LastOrDefaultAsync + + + public virtual IObservable LastOrDefaultAsync(IObservable source) + { + return new LastOrDefaultAsync.Sequence(source); + } + + public virtual IObservable LastOrDefaultAsync(IObservable source, Func predicate) + { + return new LastOrDefaultAsync.Predicate(source, predicate); + } + + #endregion + + #region + LongCount + + + public virtual IObservable LongCount(IObservable source) + { + return new LongCount.All(source); + } + + public virtual IObservable LongCount(IObservable source, Func predicate) + { + return new LongCount.Predicate(source, predicate); + } + + #endregion + + #region + Max + + + public virtual IObservable Max(IObservable source) + { + // BREAKING CHANGE v2 > v1.x - Behavior for reference types + return new Max(source, Comparer.Default); + } + + public virtual IObservable Max(IObservable source, IComparer comparer) + { + // BREAKING CHANGE v2 > v1.x - Behavior for reference types + return new Max(source, comparer); + } + + public virtual IObservable Max(IObservable source) + { + return new MaxDouble(source); + } + + public virtual IObservable Max(IObservable source) + { + return new MaxSingle(source); + } + + public virtual IObservable Max(IObservable source) + { + return new MaxDecimal(source); + } + + public virtual IObservable Max(IObservable source) + { + return new MaxInt32(source); + } + + public virtual IObservable Max(IObservable source) + { + return new MaxInt64(source); + } + + public virtual IObservable Max(IObservable source) + { + return new MaxDoubleNullable(source); + } + + public virtual IObservable Max(IObservable source) + { + return new MaxSingleNullable(source); + } + + public virtual IObservable Max(IObservable source) + { + return new MaxDecimalNullable(source); + } + + public virtual IObservable Max(IObservable source) + { + return new MaxInt32Nullable(source); + } + + public virtual IObservable Max(IObservable source) + { + return new MaxInt64Nullable(source); + } + + public virtual IObservable Max(IObservable source, Func selector) + { + return Max(Select(source, selector)); + } + + public virtual IObservable Max(IObservable source, Func selector, IComparer comparer) + { + return Max(Select(source, selector), comparer); + } + + public virtual IObservable Max(IObservable source, Func selector) + { + return Max(Select(source, selector)); + } + + public virtual IObservable Max(IObservable source, Func selector) + { + return Max(Select(source, selector)); + } + + public virtual IObservable Max(IObservable source, Func selector) + { + return Max(Select(source, selector)); + } + + public virtual IObservable Max(IObservable source, Func selector) + { + return Max(Select(source, selector)); + } + + public virtual IObservable Max(IObservable source, Func selector) + { + return Max(Select(source, selector)); + } + + public virtual IObservable Max(IObservable source, Func selector) + { + return Max(Select(source, selector)); + } + + public virtual IObservable Max(IObservable source, Func selector) + { + return Max(Select(source, selector)); + } + + public virtual IObservable Max(IObservable source, Func selector) + { + return Max(Select(source, selector)); + } + + public virtual IObservable Max(IObservable source, Func selector) + { + return Max(Select(source, selector)); + } + + public virtual IObservable Max(IObservable source, Func selector) + { + return Max(Select(source, selector)); + } + + #endregion + + #region + MaxBy + + + public virtual IObservable> MaxBy(IObservable source, Func keySelector) + { + return new MaxBy(source, keySelector, Comparer.Default); + } + + public virtual IObservable> MaxBy(IObservable source, Func keySelector, IComparer comparer) + { + return new MaxBy(source, keySelector, comparer); + } + + #endregion + + #region + Min + + + public virtual IObservable Min(IObservable source) + { + // BREAKING CHANGE v2 > v1.x - Behavior for reference types + return new Min(source, Comparer.Default); + } + + public virtual IObservable Min(IObservable source, IComparer comparer) + { + // BREAKING CHANGE v2 > v1.x - Behavior for reference types + return new Min(source, comparer); + } + + public virtual IObservable Min(IObservable source) + { + return new MinDouble(source); + } + + public virtual IObservable Min(IObservable source) + { + return new MinSingle(source); + } + + public virtual IObservable Min(IObservable source) + { + return new MinDecimal(source); + } + + public virtual IObservable Min(IObservable source) + { + return new MinInt32(source); + } + + public virtual IObservable Min(IObservable source) + { + return new MinInt64(source); + } + + public virtual IObservable Min(IObservable source) + { + return new MinDoubleNullable(source); + } + + public virtual IObservable Min(IObservable source) + { + return new MinSingleNullable(source); + } + + public virtual IObservable Min(IObservable source) + { + return new MinDecimalNullable(source); + } + + public virtual IObservable Min(IObservable source) + { + return new MinInt32Nullable(source); + } + + public virtual IObservable Min(IObservable source) + { + return new MinInt64Nullable(source); + } + + public virtual IObservable Min(IObservable source, Func selector) + { + return Min(Select(source, selector)); + } + + public virtual IObservable Min(IObservable source, Func selector, IComparer comparer) + { + return Min(Select(source, selector), comparer); + } + + public virtual IObservable Min(IObservable source, Func selector) + { + return Min(Select(source, selector)); + } + + public virtual IObservable Min(IObservable source, Func selector) + { + return Min(Select(source, selector)); + } + + public virtual IObservable Min(IObservable source, Func selector) + { + return Min(Select(source, selector)); + } + + public virtual IObservable Min(IObservable source, Func selector) + { + return Min(Select(source, selector)); + } + + public virtual IObservable Min(IObservable source, Func selector) + { + return Min(Select(source, selector)); + } + + public virtual IObservable Min(IObservable source, Func selector) + { + return Min(Select(source, selector)); + } + + public virtual IObservable Min(IObservable source, Func selector) + { + return Min(Select(source, selector)); + } + + public virtual IObservable Min(IObservable source, Func selector) + { + return Min(Select(source, selector)); + } + + public virtual IObservable Min(IObservable source, Func selector) + { + return Min(Select(source, selector)); + } + + public virtual IObservable Min(IObservable source, Func selector) + { + return Min(Select(source, selector)); + } + + #endregion + + #region + MinBy + + + public virtual IObservable> MinBy(IObservable source, Func keySelector) + { + return new MinBy(source, keySelector, Comparer.Default); + } + + public virtual IObservable> MinBy(IObservable source, Func keySelector, IComparer comparer) + { + return new MinBy(source, keySelector, comparer); + } + + #endregion + + #region + SequenceEqual + + + public virtual IObservable SequenceEqual(IObservable first, IObservable second) + { + return new SequenceEqual.Observable(first, second, EqualityComparer.Default); + } + + public virtual IObservable SequenceEqual(IObservable first, IObservable second, IEqualityComparer comparer) + { + return new SequenceEqual.Observable(first, second, comparer); + } + + public virtual IObservable SequenceEqual(IObservable first, IEnumerable second) + { + return new SequenceEqual.Enumerable(first, second, EqualityComparer.Default); + } + + public virtual IObservable SequenceEqual(IObservable first, IEnumerable second, IEqualityComparer comparer) + { + return new SequenceEqual.Enumerable(first, second, comparer); + } + + #endregion + + #region + SingleAsync + + + public virtual IObservable SingleAsync(IObservable source) + { + return new SingleAsync.Sequence(source); + } + + public virtual IObservable SingleAsync(IObservable source, Func predicate) + { + return new SingleAsync.Predicate(source, predicate); + } + + #endregion + + #region + SingleOrDefaultAsync + + + public virtual IObservable SingleOrDefaultAsync(IObservable source) + { + return new SingleOrDefaultAsync.Sequence(source); + } + + public virtual IObservable SingleOrDefaultAsync(IObservable source, Func predicate) + { + return new SingleOrDefaultAsync.Predicate(source, predicate); + } + + #endregion + + #region + Sum + + + public virtual IObservable Sum(IObservable source) + { + return new SumDouble(source); + } + + public virtual IObservable Sum(IObservable source) + { + return new SumSingle(source); + } + + public virtual IObservable Sum(IObservable source) + { + return new SumDecimal(source); + } + + public virtual IObservable Sum(IObservable source) + { + return new SumInt32(source); + } + + public virtual IObservable Sum(IObservable source) + { + return new SumInt64(source); + } + + public virtual IObservable Sum(IObservable source) + { + return new SumDoubleNullable(source); + } + + public virtual IObservable Sum(IObservable source) + { + return new SumSingleNullable(source); + } + + public virtual IObservable Sum(IObservable source) + { + return new SumDecimalNullable(source); + } + + public virtual IObservable Sum(IObservable source) + { + return new SumInt32Nullable(source); + } + + public virtual IObservable Sum(IObservable source) + { + return new SumInt64Nullable(source); + } + + public virtual IObservable Sum(IObservable source, Func selector) + { + return Sum(Select(source, selector)); + } + + public virtual IObservable Sum(IObservable source, Func selector) + { + return Sum(Select(source, selector)); + } + + public virtual IObservable Sum(IObservable source, Func selector) + { + return Sum(Select(source, selector)); + } + + public virtual IObservable Sum(IObservable source, Func selector) + { + return Sum(Select(source, selector)); + } + + public virtual IObservable Sum(IObservable source, Func selector) + { + return Sum(Select(source, selector)); + } + + public virtual IObservable Sum(IObservable source, Func selector) + { + return Sum(Select(source, selector)); + } + + public virtual IObservable Sum(IObservable source, Func selector) + { + return Sum(Select(source, selector)); + } + + public virtual IObservable Sum(IObservable source, Func selector) + { + return Sum(Select(source, selector)); + } + + public virtual IObservable Sum(IObservable source, Func selector) + { + return Sum(Select(source, selector)); + } + + public virtual IObservable Sum(IObservable source, Func selector) + { + return Sum(Select(source, selector)); + } + + #endregion + + #region + ToArray + + + public virtual IObservable ToArray(IObservable source) + { + return new ToArray(source); + } + + #endregion + + #region + ToDictionary + + + public virtual IObservable> ToDictionary(IObservable source, Func keySelector, Func elementSelector, IEqualityComparer comparer) + where TKey : notnull + { + return new ToDictionary(source, keySelector, elementSelector, comparer); + } + + public virtual IObservable> ToDictionary(IObservable source, Func keySelector, Func elementSelector) + where TKey : notnull + { + return new ToDictionary(source, keySelector, elementSelector, EqualityComparer.Default); + } + + public virtual IObservable> ToDictionary(IObservable source, Func keySelector, IEqualityComparer comparer) + where TKey : notnull + { + return new ToDictionary(source, keySelector, x => x, comparer); + } + + public virtual IObservable> ToDictionary(IObservable source, Func keySelector) + where TKey : notnull + { + return new ToDictionary(source, keySelector, x => x, EqualityComparer.Default); + } + + #endregion + + #region + ToList + + + public virtual IObservable> ToList(IObservable source) + { + return new ToList(source); + } + + #endregion + + #region + ToLookup + + + public virtual IObservable> ToLookup(IObservable source, Func keySelector, Func elementSelector, IEqualityComparer comparer) + { + return new ToLookup(source, keySelector, elementSelector, comparer); + } + + public virtual IObservable> ToLookup(IObservable source, Func keySelector, IEqualityComparer comparer) + { + return new ToLookup(source, keySelector, x => x, comparer); + } + + public virtual IObservable> ToLookup(IObservable source, Func keySelector, Func elementSelector) + { + return new ToLookup(source, keySelector, elementSelector, EqualityComparer.Default); + } + + public virtual IObservable> ToLookup(IObservable source, Func keySelector) + { + return new ToLookup(source, keySelector, x => x, EqualityComparer.Default); + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/QueryLanguage.Async.cs b/LibExternal/System.Reactive/Linq/QueryLanguage.Async.cs new file mode 100644 index 0000000..3339c18 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QueryLanguage.Async.cs @@ -0,0 +1,1842 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reactive.Disposables; +using System.Reactive.Subjects; +using System.Reactive.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Reactive.Linq +{ + internal partial class QueryLanguage + { + #region FromAsyncPattern + + #region Func + + public virtual Func> FromAsyncPattern(Func begin, Func end) + { + return () => + { + var subject = new AsyncSubject(); + try + { + begin(iar => + { + // Note: Even if the callback completes synchronously, outgoing On* calls + // cannot throw in user code since there can't be any subscribers + // to the AsyncSubject yet. Therefore, there is no need to protect + // against exceptions that'd be caught below and sent (incorrectly) + // into the Observable.Throw sequence being constructed. + TResult result; + try + { + result = end(iar); + } + catch (Exception exception) + { + subject.OnError(exception); + return; + } + subject.OnNext(result); + subject.OnCompleted(); + }, null); + } + catch (Exception exception) + { + return Observable.Throw(exception, SchedulerDefaults.AsyncConversions); + } + return subject.AsObservable(); + }; + } + + public virtual Func> FromAsyncPattern(Func begin, Func end) + { + return x => + { + var subject = new AsyncSubject(); + try + { + begin(x, iar => + { + // See remark on FromAsyncPattern. + TResult result; + try + { + result = end(iar); + } + catch (Exception exception) + { + subject.OnError(exception); + return; + } + subject.OnNext(result); + subject.OnCompleted(); + }, null); + } + catch (Exception exception) + { + return Observable.Throw(exception, SchedulerDefaults.AsyncConversions); + } + return subject.AsObservable(); + }; + } + + public virtual Func> FromAsyncPattern(Func begin, Func end) + { + return (x, y) => + { + var subject = new AsyncSubject(); + try + { + begin(x, y, iar => + { + // See remark on FromAsyncPattern. + TResult result; + try + { + result = end(iar); + } + catch (Exception exception) + { + subject.OnError(exception); + return; + } + subject.OnNext(result); + subject.OnCompleted(); + }, null); + } + catch (Exception exception) + { + return Observable.Throw(exception, SchedulerDefaults.AsyncConversions); + } + return subject.AsObservable(); + }; + } + + public virtual Func> FromAsyncPattern(Func begin, Func end) + { + return (x, y, z) => + { + var subject = new AsyncSubject(); + try + { + begin(x, y, z, iar => + { + // See remark on FromAsyncPattern. + TResult result; + try + { + result = end(iar); + } + catch (Exception exception) + { + subject.OnError(exception); + return; + } + subject.OnNext(result); + subject.OnCompleted(); + }, null); + } + catch (Exception exception) + { + return Observable.Throw(exception, SchedulerDefaults.AsyncConversions); + } + return subject.AsObservable(); + }; + } + + public virtual Func> FromAsyncPattern(Func begin, Func end) + { + return (x, y, z, a) => + { + var subject = new AsyncSubject(); + try + { + begin(x, y, z, a, iar => + { + // See remark on FromAsyncPattern. + TResult result; + try + { + result = end(iar); + } + catch (Exception exception) + { + subject.OnError(exception); + return; + } + subject.OnNext(result); + subject.OnCompleted(); + }, null); + } + catch (Exception exception) + { + return Observable.Throw(exception, SchedulerDefaults.AsyncConversions); + } + return subject.AsObservable(); + }; + } + + public virtual Func> FromAsyncPattern(Func begin, Func end) + { + return (x, y, z, a, b) => + { + var subject = new AsyncSubject(); + try + { + begin(x, y, z, a, b, iar => + { + // See remark on FromAsyncPattern. + TResult result; + try + { + result = end(iar); + } + catch (Exception exception) + { + subject.OnError(exception); + return; + } + subject.OnNext(result); + subject.OnCompleted(); + }, null); + } + catch (Exception exception) + { + return Observable.Throw(exception, SchedulerDefaults.AsyncConversions); + } + return subject.AsObservable(); + }; + } + + public virtual Func> FromAsyncPattern(Func begin, Func end) + { + return (x, y, z, a, b, c) => + { + var subject = new AsyncSubject(); + try + { + begin(x, y, z, a, b, c, iar => + { + // See remark on FromAsyncPattern. + TResult result; + try + { + result = end(iar); + } + catch (Exception exception) + { + subject.OnError(exception); + return; + } + subject.OnNext(result); + subject.OnCompleted(); + }, null); + } + catch (Exception exception) + { + return Observable.Throw(exception, SchedulerDefaults.AsyncConversions); + } + return subject.AsObservable(); + }; + } + + public virtual Func> FromAsyncPattern(Func begin, Func end) + { + return (x, y, z, a, b, c, d) => + { + var subject = new AsyncSubject(); + try + { + begin(x, y, z, a, b, c, d, iar => + { + // See remark on FromAsyncPattern. + TResult result; + try + { + result = end(iar); + } + catch (Exception exception) + { + subject.OnError(exception); + return; + } + subject.OnNext(result); + subject.OnCompleted(); + }, null); + } + catch (Exception exception) + { + return Observable.Throw(exception, SchedulerDefaults.AsyncConversions); + } + return subject.AsObservable(); + }; + } + + public virtual Func> FromAsyncPattern(Func begin, Func end) + { + return (x, y, z, a, b, c, d, e) => + { + var subject = new AsyncSubject(); + try + { + begin(x, y, z, a, b, c, d, e, iar => + { + // See remark on FromAsyncPattern. + TResult result; + try + { + result = end(iar); + } + catch (Exception exception) + { + subject.OnError(exception); + return; + } + subject.OnNext(result); + subject.OnCompleted(); + }, null); + } + catch (Exception exception) + { + return Observable.Throw(exception, SchedulerDefaults.AsyncConversions); + } + return subject.AsObservable(); + }; + } + + public virtual Func> FromAsyncPattern(Func begin, Func end) + { + return (x, y, z, a, b, c, d, e, f) => + { + var subject = new AsyncSubject(); + try + { + begin(x, y, z, a, b, c, d, e, f, iar => + { + // See remark on FromAsyncPattern. + TResult result; + try + { + result = end(iar); + } + catch (Exception exception) + { + subject.OnError(exception); + return; + } + subject.OnNext(result); + subject.OnCompleted(); + }, null); + } + catch (Exception exception) + { + return Observable.Throw(exception, SchedulerDefaults.AsyncConversions); + } + return subject.AsObservable(); + }; + } + + public virtual Func> FromAsyncPattern(Func begin, Func end) + { + return (x, y, z, a, b, c, d, e, f, g) => + { + var subject = new AsyncSubject(); + try + { + begin(x, y, z, a, b, c, d, e, f, g, iar => + { + // See remark on FromAsyncPattern. + TResult result; + try + { + result = end(iar); + } + catch (Exception exception) + { + subject.OnError(exception); + return; + } + subject.OnNext(result); + subject.OnCompleted(); + }, null); + } + catch (Exception exception) + { + return Observable.Throw(exception, SchedulerDefaults.AsyncConversions); + } + return subject.AsObservable(); + }; + } + + public virtual Func> FromAsyncPattern(Func begin, Func end) + { + return (x, y, z, a, b, c, d, e, f, g, h) => + { + var subject = new AsyncSubject(); + try + { + begin(x, y, z, a, b, c, d, e, f, g, h, iar => + { + // See remark on FromAsyncPattern. + TResult result; + try + { + result = end(iar); + } + catch (Exception exception) + { + subject.OnError(exception); + return; + } + subject.OnNext(result); + subject.OnCompleted(); + }, null); + } + catch (Exception exception) + { + return Observable.Throw(exception, SchedulerDefaults.AsyncConversions); + } + return subject.AsObservable(); + }; + } + + public virtual Func> FromAsyncPattern(Func begin, Func end) + { + return (x, y, z, a, b, c, d, e, f, g, h, i) => + { + var subject = new AsyncSubject(); + try + { + begin(x, y, z, a, b, c, d, e, f, g, h, i, iar => + { + // See remark on FromAsyncPattern. + TResult result; + try + { + result = end(iar); + } + catch (Exception exception) + { + subject.OnError(exception); + return; + } + subject.OnNext(result); + subject.OnCompleted(); + }, null); + } + catch (Exception exception) + { + return Observable.Throw(exception, SchedulerDefaults.AsyncConversions); + } + return subject.AsObservable(); + }; + } + + public virtual Func> FromAsyncPattern(Func begin, Func end) + { + return (x, y, z, a, b, c, d, e, f, g, h, i, j) => + { + var subject = new AsyncSubject(); + try + { + begin(x, y, z, a, b, c, d, e, f, g, h, i, j, iar => + { + // See remark on FromAsyncPattern. + TResult result; + try + { + result = end(iar); + } + catch (Exception exception) + { + subject.OnError(exception); + return; + } + subject.OnNext(result); + subject.OnCompleted(); + }, null); + } + catch (Exception exception) + { + return Observable.Throw(exception, SchedulerDefaults.AsyncConversions); + } + return subject.AsObservable(); + }; + } + + public virtual Func> FromAsyncPattern(Func begin, Func end) + { + return (x, y, z, a, b, c, d, e, f, g, h, i, j, k) => + { + var subject = new AsyncSubject(); + try + { + begin(x, y, z, a, b, c, d, e, f, g, h, i, j, k, iar => + { + // See remark on FromAsyncPattern. + TResult result; + try + { + result = end(iar); + } + catch (Exception exception) + { + subject.OnError(exception); + return; + } + subject.OnNext(result); + subject.OnCompleted(); + }, null); + } + catch (Exception exception) + { + return Observable.Throw(exception, SchedulerDefaults.AsyncConversions); + } + return subject.AsObservable(); + }; + } + + #endregion + + #region Action + + public virtual Func> FromAsyncPattern(Func begin, Action end) + { + return FromAsyncPattern(begin, iar => + { + end(iar); + return Unit.Default; + }); + } + + public virtual Func> FromAsyncPattern(Func begin, Action end) + { + return FromAsyncPattern(begin, iar => + { + end(iar); + return Unit.Default; + }); + } + + public virtual Func> FromAsyncPattern(Func begin, Action end) + { + return FromAsyncPattern(begin, iar => + { + end(iar); + return Unit.Default; + }); + } + + public virtual Func> FromAsyncPattern(Func begin, Action end) + { + return FromAsyncPattern(begin, iar => + { + end(iar); + return Unit.Default; + }); + } + + public virtual Func> FromAsyncPattern(Func begin, Action end) + { + return FromAsyncPattern(begin, iar => + { + end(iar); + return Unit.Default; + }); + } + + public virtual Func> FromAsyncPattern(Func begin, Action end) + { + return FromAsyncPattern(begin, iar => + { + end(iar); + return Unit.Default; + }); + } + + public virtual Func> FromAsyncPattern(Func begin, Action end) + { + return FromAsyncPattern(begin, iar => + { + end(iar); + return Unit.Default; + }); + } + + public virtual Func> FromAsyncPattern(Func begin, Action end) + { + return FromAsyncPattern(begin, iar => + { + end(iar); + return Unit.Default; + }); + } + + public virtual Func> FromAsyncPattern(Func begin, Action end) + { + return FromAsyncPattern(begin, iar => + { + end(iar); + return Unit.Default; + }); + } + + public virtual Func> FromAsyncPattern(Func begin, Action end) + { + return FromAsyncPattern(begin, iar => + { + end(iar); + return Unit.Default; + }); + } + + public virtual Func> FromAsyncPattern(Func begin, Action end) + { + return FromAsyncPattern(begin, iar => + { + end(iar); + return Unit.Default; + }); + } + + public virtual Func> FromAsyncPattern(Func begin, Action end) + { + return FromAsyncPattern(begin, iar => + { + end(iar); + return Unit.Default; + }); + } + + public virtual Func> FromAsyncPattern(Func begin, Action end) + { + return FromAsyncPattern(begin, iar => + { + end(iar); + return Unit.Default; + }); + } + + public virtual Func> FromAsyncPattern(Func begin, Action end) + { + return FromAsyncPattern(begin, iar => + { + end(iar); + return Unit.Default; + }); + } + + public virtual Func> FromAsyncPattern(Func begin, Action end) + { + return FromAsyncPattern(begin, iar => + { + end(iar); + return Unit.Default; + }); + } + + #endregion + + #endregion + + #region Start[Async] + + #region Func + + public virtual IObservable Start(Func function) + { + return ToAsync(function)(); + } + + public virtual IObservable Start(Func function, IScheduler scheduler) + { + return ToAsync(function, scheduler)(); + } + + public virtual IObservable StartAsync(Func> functionAsync) + { + return StartAsyncImpl(functionAsync, new TaskObservationOptions.Value(null, ignoreExceptionsAfterUnsubscribe: false)); + } + + public virtual IObservable StartAsync(Func> functionAsync, in TaskObservationOptions.Value options) + { + return StartAsyncImpl(functionAsync, options); + } + + private IObservable StartAsyncImpl(Func> functionAsync, in TaskObservationOptions.Value options) + { + Task task; + try + { + task = functionAsync(); + } + catch (Exception exception) + { + return Throw(exception); + } + + return task.ToObservable(options); + } + + public virtual IObservable StartAsync(Func> functionAsync) + { + return StartAsyncImpl(functionAsync, new TaskObservationOptions.Value(null, false)); + } + + public virtual IObservable StartAsync(Func> functionAsync, in TaskObservationOptions.Value options) + { + return StartAsyncImpl(functionAsync, options); + } + + private IObservable StartAsyncImpl(Func> functionAsync, in TaskObservationOptions.Value options) + { + var cancellable = new CancellationDisposable(); + + Task task; + try + { + task = functionAsync(cancellable.Token); + } + catch (Exception exception) + { + return Throw(exception); + } + + var result = task.ToObservable(options); + + return new StartAsyncObservable(cancellable, result); + } + + private sealed class StartAsyncObservable : ObservableBase + { + private readonly CancellationDisposable _cancellable; + private readonly IObservable _result; + + public StartAsyncObservable(CancellationDisposable cancellable, IObservable result) + { + _cancellable = cancellable; + _result = result; + } + + protected override IDisposable SubscribeCore(IObserver observer) + { + // + // [OK] Use of unsafe Subscribe: result is an AsyncSubject. + // + var subscription = _result.Subscribe/*Unsafe*/(observer); + return StableCompositeDisposable.Create(_cancellable, subscription); + } + } + + #endregion + + #region Action + + public virtual IObservable Start(Action action) + { + return ToAsync(action, SchedulerDefaults.AsyncConversions)(); + } + + public virtual IObservable Start(Action action, IScheduler scheduler) + { + return ToAsync(action, scheduler)(); + } + + public virtual IObservable StartAsync(Func actionAsync) + { + return StartAsyncImpl(actionAsync, new TaskObservationOptions.Value(null, ignoreExceptionsAfterUnsubscribe: false)); + } + + public virtual IObservable StartAsync(Func actionAsync, in TaskObservationOptions.Value options) + { + return StartAsyncImpl(actionAsync, options); + } + + private IObservable StartAsyncImpl(Func actionAsync, in TaskObservationOptions.Value options) + { + Task task; + try + { + task = actionAsync(); + } + catch (Exception exception) + { + return Throw(exception); + } + + return task.ToObservable(options); + } + + public virtual IObservable StartAsync(Func actionAsync) + { + return StartAsyncImpl(actionAsync, new TaskObservationOptions.Value(null, ignoreExceptionsAfterUnsubscribe: false)); + } + + public virtual IObservable StartAsync(Func actionAsync, in TaskObservationOptions.Value options) + { + return StartAsyncImpl(actionAsync, options); + } + + private IObservable StartAsyncImpl(Func actionAsync, in TaskObservationOptions.Value options) + { + var cancellable = new CancellationDisposable(); + + Task task; + try + { + task = actionAsync(cancellable.Token); + } + catch (Exception exception) + { + return Throw(exception); + } + + var result = task.ToObservable(options); + + return new StartAsyncObservable(cancellable, result); + } + + #endregion + + #endregion + + #region FromAsync + + #region Func + + public virtual IObservable FromAsync(Func> functionAsync) + { + return Defer(() => StartAsync(functionAsync)); + } + + public virtual IObservable FromAsync(Func> functionAsync) + { + return Defer(() => StartAsync(functionAsync)); + } + + public virtual IObservable FromAsync(Func> functionAsync, TaskObservationOptions.Value options) + { + return Defer(() => StartAsync(functionAsync, options)); + } + + public virtual IObservable FromAsync(Func> functionAsync, TaskObservationOptions.Value options) + { + return Defer(() => StartAsync(functionAsync, options)); + } + + #endregion + + #region Action + + public virtual IObservable FromAsync(Func actionAsync) + { + return Defer(() => StartAsync(actionAsync)); + } + + public virtual IObservable FromAsync(Func actionAsync) + { + return Defer(() => StartAsync(actionAsync)); + } + + public virtual IObservable FromAsync(Func actionAsync, TaskObservationOptions.Value options) + { + return Defer(() => StartAsync(actionAsync, options)); + } + + public virtual IObservable FromAsync(Func actionAsync, TaskObservationOptions.Value options) + { + return Defer(() => StartAsync(actionAsync, options)); + } + + #endregion + + #endregion + + #region ToAsync + + #region Func + + public virtual Func> ToAsync(Func function) + { + return ToAsync(function, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Func function, IScheduler scheduler) + { + return () => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((function, subject), state => + { + TResult result; + try + { + result = state.function(); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(result); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Func function) + { + return ToAsync(function, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Func function, IScheduler scheduler) + { + return first => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((function, subject, first), state => + { + TResult result; + try + { + result = state.function(state.first); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(result); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Func function) + { + return ToAsync(function, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Func function, IScheduler scheduler) + { + return (first, second) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, function, first, second), state => + { + TResult result; + try + { + result = state.function(state.first, state.second); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(result); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Func function) + { + return ToAsync(function, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Func function, IScheduler scheduler) + { + return (first, second, third) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, function, first, second, third), state => + { + TResult result; + try + { + result = state.function(state.first, state.second, state.third); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(result); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Func function) + { + return ToAsync(function, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Func function, IScheduler scheduler) + { + return (first, second, third, fourth) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, function, first, second, third, fourth), state => + { + TResult result; + try + { + result = state.function(state.first, state.second, state.third, state.fourth); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(result); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Func function) + { + return ToAsync(function, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Func function, IScheduler scheduler) + { + return (first, second, third, fourth, fifth) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, function, first, second, third, fourth, fifth), state => + { + TResult result; + try + { + result = state.function(state.first, state.second, state.third, state.fourth, state.fifth); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(result); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Func function) + { + return ToAsync(function, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Func function, IScheduler scheduler) + { + return (first, second, third, fourth, fifth, sixth) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, function, first, second, third, fourth, fifth, sixth), state => + { + TResult result; + try + { + result = state.function(state.first, state.second, state.third, state.fourth, state.fifth, state.sixth); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(result); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Func function) + { + return ToAsync(function, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Func function, IScheduler scheduler) + { + return (first, second, third, fourth, fifth, sixth, seventh) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, function, first, second, third, fourth, fifth, sixth, seventh), state => + { + TResult result; + try + { + result = state.function(state.first, state.second, state.third, state.fourth, state.fifth, state.sixth, state.seventh); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(result); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Func function) + { + return ToAsync(function, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Func function, IScheduler scheduler) + { + return (first, second, third, fourth, fifth, sixth, seventh, eight) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, function, first, second, third, fourth, fifth, sixth, seventh, eight), state => + { + TResult result; + try + { + result = state.function(state.first, state.second, state.third, state.fourth, state.fifth, state.sixth, state.seventh, state.eight); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(result); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Func function) + { + return ToAsync(function, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Func function, IScheduler scheduler) + { + return (first, second, third, fourth, fifth, sixth, seventh, eight, ninth) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, function, first, second, third, fourth, fifth, sixth, seventh, eight, ninth), state => + { + TResult result; + try + { + result = state.function(state.first, state.second, state.third, state.fourth, state.fifth, state.sixth, state.seventh, state.eight, state.ninth); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(result); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Func function) + { + return ToAsync(function, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Func function, IScheduler scheduler) + { + return (first, second, third, fourth, fifth, sixth, seventh, eight, ninth, tenth) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, function, first, second, third, fourth, fifth, sixth, seventh, eight, ninth, tenth), state => + { + TResult result; + try + { + result = state.function(state.first, state.second, state.third, state.fourth, state.fifth, state.sixth, state.seventh, state.eight, state.ninth, state.tenth); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(result); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Func function) + { + return ToAsync(function, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Func function, IScheduler scheduler) + { + return (first, second, third, fourth, fifth, sixth, seventh, eight, ninth, tenth, eleventh) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, function, first, second, third, fourth, fifth, sixth, seventh, eight, ninth, tenth, eleventh), state => + { + TResult result; + try + { + result = state.function(state.first, state.second, state.third, state.fourth, state.fifth, state.sixth, state.seventh, state.eight, state.ninth, state.tenth, state.eleventh); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(result); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Func function) + { + return ToAsync(function, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Func function, IScheduler scheduler) + { + return (first, second, third, fourth, fifth, sixth, seventh, eight, ninth, tenth, eleventh, twelfth) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, function, first, second, third, fourth, fifth, sixth, seventh, eight, ninth, tenth, eleventh, twelfth), state => + { + TResult result; + try + { + result = state.function(state.first, state.second, state.third, state.fourth, state.fifth, state.sixth, state.seventh, state.eight, state.ninth, state.tenth, state.eleventh, state.twelfth); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(result); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Func function) + { + return ToAsync(function, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Func function, IScheduler scheduler) + { + return (first, second, third, fourth, fifth, sixth, seventh, eight, ninth, tenth, eleventh, twelfth, thirteenth) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, function, first, second, third, fourth, fifth, sixth, seventh, eight, ninth, tenth, eleventh, twelfth, thirteenth), state => + { + TResult result; + try + { + result = state.function(state.first, state.second, state.third, state.fourth, state.fifth, state.sixth, state.seventh, state.eight, state.ninth, state.tenth, state.eleventh, state.twelfth, state.thirteenth); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(result); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Func function) + { + return ToAsync(function, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Func function, IScheduler scheduler) + { + return (first, second, third, fourth, fifth, sixth, seventh, eight, ninth, tenth, eleventh, twelfth, thirteenth, fourteenth) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, function, first, second, third, fourth, fifth, sixth, seventh, eight, ninth, tenth, eleventh, twelfth, thirteenth, fourteenth), state => + { + TResult result; + try + { + result = state.function(state.first, state.second, state.third, state.fourth, state.fifth, state.sixth, state.seventh, state.eight, state.ninth, state.tenth, state.eleventh, state.twelfth, state.thirteenth, state.fourteenth); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(result); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Func function) + { + return ToAsync(function, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Func function, IScheduler scheduler) + { + return (first, second, third, fourth, fifth, sixth, seventh, eight, ninth, tenth, eleventh, twelfth, thirteenth, fourteenth, fifteenth) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, function, first, second, third, fourth, fifth, sixth, seventh, eight, ninth, tenth, eleventh, twelfth, thirteenth, fourteenth, fifteenth), state => + { + TResult result; + try + { + result = state.function(state.first, state.second, state.third, state.fourth, state.fifth, state.sixth, state.seventh, state.eight, state.ninth, state.tenth, state.eleventh, state.twelfth, state.thirteenth, state.fourteenth, state.fifteenth); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(result); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Func function) + { + return ToAsync(function, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Func function, IScheduler scheduler) + { + return (first, second, third, fourth, fifth, sixth, seventh, eight, ninth, tenth, eleventh, twelfth, thirteenth, fourteenth, fifteenth, sixteenth) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, function, first, second, third, fourth, fifth, sixth, seventh, eight, ninth, tenth, eleventh, twelfth, thirteenth, fourteenth, fifteenth, sixteenth), state => + { + TResult result; + try + { + result = state.function(state.first, state.second, state.third, state.fourth, state.fifth, state.sixth, state.seventh, state.eight, state.ninth, state.tenth, state.eleventh, state.twelfth, state.thirteenth, state.fourteenth, state.fifteenth, state.sixteenth); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(result); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + #endregion + + #region Action + + public virtual Func> ToAsync(Action action) + { + return ToAsync(action, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Action action, IScheduler scheduler) + { + return () => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, action), state => + { + try + { + state.action(); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(Unit.Default); + state.subject.OnCompleted(); + }); + + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Action action) + { + return ToAsync(action, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Action action, IScheduler scheduler) + { + return first => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, action, first), state => + { + try + { + state.action(state.first); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(Unit.Default); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Action action) + { + return ToAsync(action, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Action action, IScheduler scheduler) + { + return (first, second) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, action, first, second), state => + { + try + { + state.action(state.first, state.second); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(Unit.Default); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Action action) + { + return ToAsync(action, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Action action, IScheduler scheduler) + { + return (first, second, third) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, action, first, second, third), state => + { + try + { + state.action(state.first, state.second, state.third); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(Unit.Default); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Action action) + { + return ToAsync(action, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Action action, IScheduler scheduler) + { + return (first, second, third, fourth) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, action, first, second, third, fourth), state => + { + try + { + state.action(state.first, state.second, state.third, state.fourth); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(Unit.Default); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Action action) + { + return ToAsync(action, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Action action, IScheduler scheduler) + { + return (first, second, third, fourth, fifth) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, action, first, second, third, fourth, fifth), state => + { + try + { + state.action(state.first, state.second, state.third, state.fourth, state.fifth); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(Unit.Default); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Action action) + { + return ToAsync(action, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Action action, IScheduler scheduler) + { + return (first, second, third, fourth, fifth, sixth) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, action, first, second, third, fourth, fifth, sixth), state => + { + try + { + state.action(state.first, state.second, state.third, state.fourth, state.fifth, state.sixth); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(Unit.Default); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Action action) + { + return ToAsync(action, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Action action, IScheduler scheduler) + { + return (first, second, third, fourth, fifth, sixth, seventh) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, action, first, second, third, fourth, fifth, sixth, seventh), state => + { + try + { + state.action(state.first, state.second, state.third, state.fourth, state.fifth, state.sixth, state.seventh); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(Unit.Default); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Action action) + { + return ToAsync(action, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Action action, IScheduler scheduler) + { + return (first, second, third, fourth, fifth, sixth, seventh, eighth) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, action, first, second, third, fourth, fifth, sixth, seventh, eighth), state => + { + try + { + state.action(state.first, state.second, state.third, state.fourth, state.fifth, state.sixth, state.seventh, state.eighth); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(Unit.Default); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Action action) + { + return ToAsync(action, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Action action, IScheduler scheduler) + { + return (first, second, third, fourth, fifth, sixth, seventh, eighth, ninth) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, action, first, second, third, fourth, fifth, sixth, seventh, eighth, ninth), state => + { + try + { + state.action(state.first, state.second, state.third, state.fourth, state.fifth, state.sixth, state.seventh, state.eighth, state.ninth); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(Unit.Default); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Action action) + { + return ToAsync(action, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Action action, IScheduler scheduler) + { + return (first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, action, first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth), state => + { + try + { + state.action(state.first, state.second, state.third, state.fourth, state.fifth, state.sixth, state.seventh, state.eighth, state.ninth, state.tenth); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(Unit.Default); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Action action) + { + return ToAsync(action, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Action action, IScheduler scheduler) + { + return (first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, action, first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh), state => + { + try + { + state.action(state.first, state.second, state.third, state.fourth, state.fifth, state.sixth, state.seventh, state.eighth, state.ninth, state.tenth, state.eleventh); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(Unit.Default); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Action action) + { + return ToAsync(action, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Action action, IScheduler scheduler) + { + return (first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, action, first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth), state => + { + try + { + state.action(state.first, state.second, state.third, state.fourth, state.fifth, state.sixth, state.seventh, state.eighth, state.ninth, state.tenth, state.eleventh, state.twelfth); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(Unit.Default); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Action action) + { + return ToAsync(action, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Action action, IScheduler scheduler) + { + return (first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth, thirteenth) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, action, first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth, thirteenth), state => + { + try + { + state.action(state.first, state.second, state.third, state.fourth, state.fifth, state.sixth, state.seventh, state.eighth, state.ninth, state.tenth, state.eleventh, state.twelfth, state.thirteenth); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(Unit.Default); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Action action) + { + return ToAsync(action, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Action action, IScheduler scheduler) + { + return (first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth, thirteenth, fourteenth) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, action, first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth, thirteenth, fourteenth), state => + { + try + { + state.action(state.first, state.second, state.third, state.fourth, state.fifth, state.sixth, state.seventh, state.eighth, state.ninth, state.tenth, state.eleventh, state.twelfth, state.thirteenth, state.fourteenth); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(Unit.Default); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Action action) + { + return ToAsync(action, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Action action, IScheduler scheduler) + { + return (first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth, thirteenth, fourteenth, fifteenth) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, action, first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth, thirteenth, fourteenth, fifteenth), state => + { + try + { + state.action(state.first, state.second, state.third, state.fourth, state.fifth, state.sixth, state.seventh, state.eighth, state.ninth, state.tenth, state.eleventh, state.twelfth, state.thirteenth, state.fourteenth, state.fifteenth); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(Unit.Default); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + public virtual Func> ToAsync(Action action) + { + return ToAsync(action, SchedulerDefaults.AsyncConversions); + } + + public virtual Func> ToAsync(Action action, IScheduler scheduler) + { + return (first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth, thirteenth, fourteenth, fifteenth, sixteenth) => + { + var subject = new AsyncSubject(); + scheduler.ScheduleAction((subject, action, first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth, thirteenth, fourteenth, fifteenth, sixteenth), state => + { + try + { + state.action(state.first, state.second, state.third, state.fourth, state.fifth, state.sixth, state.seventh, state.eighth, state.ninth, state.tenth, state.eleventh, state.twelfth, state.thirteenth, state.fourteenth, state.fifteenth, state.sixteenth); + } + catch (Exception exception) + { + state.subject.OnError(exception); + return; + } + state.subject.OnNext(Unit.Default); + state.subject.OnCompleted(); + }); + return subject.AsObservable(); + }; + } + + #endregion + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/QueryLanguage.Awaiter.cs b/LibExternal/System.Reactive/Linq/QueryLanguage.Awaiter.cs new file mode 100644 index 0000000..888a473 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QueryLanguage.Awaiter.cs @@ -0,0 +1,90 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; +using System.Reactive.Subjects; +using System.Threading; + +namespace System.Reactive.Linq +{ + internal partial class QueryLanguage + { + public virtual AsyncSubject GetAwaiter(IObservable source) + { + return RunAsync(source, CancellationToken.None); + } + + public virtual AsyncSubject GetAwaiter(IConnectableObservable source) + { + return RunAsync(source, CancellationToken.None); + } + + public virtual AsyncSubject RunAsync(IObservable source, CancellationToken cancellationToken) + { + var s = new AsyncSubject(); + + if (cancellationToken.IsCancellationRequested) + { + return Cancel(s, cancellationToken); + } + + var d = source.SubscribeSafe(s); + + if (cancellationToken.CanBeCanceled) + { + RegisterCancelation(s, d, cancellationToken); + } + + return s; + } + + public virtual AsyncSubject RunAsync(IConnectableObservable source, CancellationToken cancellationToken) + { + var s = new AsyncSubject(); + + if (cancellationToken.IsCancellationRequested) + { + return Cancel(s, cancellationToken); + } + + var d = source.SubscribeSafe(s); + var c = source.Connect(); + + if (cancellationToken.CanBeCanceled) + { + RegisterCancelation(s, StableCompositeDisposable.Create(d, c), cancellationToken); + } + + return s; + } + + private static AsyncSubject Cancel(AsyncSubject subject, CancellationToken cancellationToken) + { + subject.OnError(new OperationCanceledException(cancellationToken)); + return subject; + } + + private static void RegisterCancelation(AsyncSubject subject, IDisposable subscription, CancellationToken token) + { + // + // Separate method used to avoid heap allocation of closure when no cancellation is needed, + // e.g. when CancellationToken.None is provided to the RunAsync overloads. + // + + var ctr = token.Register(() => + { + subscription.Dispose(); + Cancel(subject, token); + }); + + // + // No null-check for ctr is needed: + // + // - CancellationTokenRegistration is a struct + // - Registration will succeed 99% of the time, no warranting an attempt to avoid spurious Subscribe calls + // + subject.Subscribe(Stubs.Ignore, _ => ctr.Dispose(), ctr.Dispose); + } + } +} diff --git a/LibExternal/System.Reactive/Linq/QueryLanguage.Binding.cs b/LibExternal/System.Reactive/Linq/QueryLanguage.Binding.cs new file mode 100644 index 0000000..1575cf2 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QueryLanguage.Binding.cs @@ -0,0 +1,201 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reactive.Subjects; + +namespace System.Reactive.Linq +{ + using ObservableImpl; + + internal partial class QueryLanguage + { + #region + Multicast + + + public virtual IConnectableObservable Multicast(IObservable source, ISubject subject) + { + return new ConnectableObservable(source, subject); + } + + public virtual IObservable Multicast(IObservable source, Func> subjectSelector, Func, IObservable> selector) + { + return new Multicast(source, subjectSelector, selector); + } + + #endregion + + #region + Publish + + + public virtual IConnectableObservable Publish(IObservable source) + { + return source.Multicast(new Subject()); + } + + public virtual IObservable Publish(IObservable source, Func, IObservable> selector) + { + return source.Multicast(() => new Subject(), selector); + } + + public virtual IConnectableObservable Publish(IObservable source, TSource initialValue) + { + return source.Multicast(new BehaviorSubject(initialValue)); + } + + public virtual IObservable Publish(IObservable source, Func, IObservable> selector, TSource initialValue) + { + return source.Multicast(() => new BehaviorSubject(initialValue), selector); + } + + #endregion + + #region + PublishLast + + + public virtual IConnectableObservable PublishLast(IObservable source) + { + return source.Multicast(new AsyncSubject()); + } + + public virtual IObservable PublishLast(IObservable source, Func, IObservable> selector) + { + return source.Multicast(() => new AsyncSubject(), selector); + } + + #endregion + + #region + RefCount + + + public virtual IObservable RefCount(IConnectableObservable source) + { + return RefCount(source, 1); + } + + public virtual IObservable RefCount(IConnectableObservable source, TimeSpan disconnectTime) + { + return RefCount(source, disconnectTime, Scheduler.Default); + } + + public virtual IObservable RefCount(IConnectableObservable source, TimeSpan disconnectTime, IScheduler scheduler) + { + return RefCount(source, 1, disconnectTime, scheduler); + } + + public virtual IObservable RefCount(IConnectableObservable source, int minObservers) + { + return new RefCount.Eager(source, minObservers); + } + + public virtual IObservable RefCount(IConnectableObservable source, int minObservers, TimeSpan disconnectTime) + { + return RefCount(source, minObservers, disconnectTime, Scheduler.Default); + } + + public virtual IObservable RefCount(IConnectableObservable source, int minObservers, TimeSpan disconnectTime, IScheduler scheduler) + { + return new RefCount.Lazy(source, disconnectTime, scheduler, minObservers); + } + + #endregion + + #region + AutoConnect + + + public virtual IObservable AutoConnect(IConnectableObservable source, int minObservers = 1, Action? onConnect = null) + { + if (minObservers <= 0) + { + var d = source.Connect(); + onConnect?.Invoke(d); + return source; + } + + return new AutoConnect(source, minObservers, onConnect); + } + + + #endregion + + #region + Replay + + + public virtual IConnectableObservable Replay(IObservable source) + { + return source.Multicast(new ReplaySubject()); + } + + public virtual IConnectableObservable Replay(IObservable source, IScheduler scheduler) + { + return source.Multicast(new ReplaySubject(scheduler)); + } + + public virtual IObservable Replay(IObservable source, Func, IObservable> selector) + { + return source.Multicast(() => new ReplaySubject(), selector); + } + + public virtual IObservable Replay(IObservable source, Func, IObservable> selector, IScheduler scheduler) + { + return source.Multicast(() => new ReplaySubject(scheduler), selector); + } + + public virtual IConnectableObservable Replay(IObservable source, TimeSpan window) + { + return source.Multicast(new ReplaySubject(window)); + } + + public virtual IObservable Replay(IObservable source, Func, IObservable> selector, TimeSpan window) + { + return source.Multicast(() => new ReplaySubject(window), selector); + } + + public virtual IConnectableObservable Replay(IObservable source, TimeSpan window, IScheduler scheduler) + { + return source.Multicast(new ReplaySubject(window, scheduler)); + } + + public virtual IObservable Replay(IObservable source, Func, IObservable> selector, TimeSpan window, IScheduler scheduler) + { + return source.Multicast(() => new ReplaySubject(window, scheduler), selector); + } + + public virtual IConnectableObservable Replay(IObservable source, int bufferSize, IScheduler scheduler) + { + return source.Multicast(new ReplaySubject(bufferSize, scheduler)); + } + + public virtual IObservable Replay(IObservable source, Func, IObservable> selector, int bufferSize, IScheduler scheduler) + { + return source.Multicast(() => new ReplaySubject(bufferSize, scheduler), selector); + } + + public virtual IConnectableObservable Replay(IObservable source, int bufferSize) + { + return source.Multicast(new ReplaySubject(bufferSize)); + } + + public virtual IObservable Replay(IObservable source, Func, IObservable> selector, int bufferSize) + { + return source.Multicast(() => new ReplaySubject(bufferSize), selector); + } + + public virtual IConnectableObservable Replay(IObservable source, int bufferSize, TimeSpan window) + { + return source.Multicast(new ReplaySubject(bufferSize, window)); + } + + public virtual IObservable Replay(IObservable source, Func, IObservable> selector, int bufferSize, TimeSpan window) + { + return source.Multicast(() => new ReplaySubject(bufferSize, window), selector); + } + + public virtual IConnectableObservable Replay(IObservable source, int bufferSize, TimeSpan window, IScheduler scheduler) + { + return source.Multicast(new ReplaySubject(bufferSize, window, scheduler)); + } + + public virtual IObservable Replay(IObservable source, Func, IObservable> selector, int bufferSize, TimeSpan window, IScheduler scheduler) + { + return source.Multicast(() => new ReplaySubject(bufferSize, window, scheduler), selector); + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/QueryLanguage.Blocking.cs b/LibExternal/System.Reactive/Linq/QueryLanguage.Blocking.cs new file mode 100644 index 0000000..e0e31c3 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QueryLanguage.Blocking.cs @@ -0,0 +1,272 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace System.Reactive.Linq +{ + + using ObservableImpl; + + internal partial class QueryLanguage + { + #region - Chunkify - + + public virtual IEnumerable> Chunkify(IObservable source) + { + return source.Collect>(() => new List(), (lst, x) => { lst.Add(x); return lst; }, _ => new List()); + } + + #endregion + + #region + Collect + + + public virtual IEnumerable Collect(IObservable source, Func newCollector, Func merge) + { + return Collect_(source, newCollector, merge, _ => newCollector()); + } + + public virtual IEnumerable Collect(IObservable source, Func getInitialCollector, Func merge, Func getNewCollector) + { + return Collect_(source, getInitialCollector, merge, getNewCollector); + } + + private static IEnumerable Collect_(IObservable source, Func getInitialCollector, Func merge, Func getNewCollector) + { + return new Collect(source, getInitialCollector, merge, getNewCollector); + } + + #endregion + + #region First + + public virtual TSource First(IObservable source) + { + return FirstOrDefaultInternal(source, throwOnEmpty: true)!; + } + + public virtual TSource First(IObservable source, Func predicate) + { + return First(Where(source, predicate)); + } + + #endregion + + #region FirstOrDefault + + [return: MaybeNull] + public virtual TSource FirstOrDefault(IObservable source) + { + return FirstOrDefaultInternal(source, throwOnEmpty: false); + } + + [return: MaybeNull] + public virtual TSource FirstOrDefault(IObservable source, Func predicate) + { + return FirstOrDefault(Where(source, predicate)); + } + + [return: MaybeNull] + private static TSource FirstOrDefaultInternal(IObservable source, bool throwOnEmpty) + { + using var consumer = new FirstBlocking(); + + using (source.Subscribe(consumer)) + { + consumer.Wait(); + } + + consumer._error?.Throw(); + + if (throwOnEmpty && !consumer._hasValue) + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + + return consumer._value; + } + + #endregion + + #region + ForEach + + + public virtual void ForEach(IObservable source, Action onNext) + { + using var sink = new ForEach.Observer(onNext); + + using (source.SubscribeSafe(sink)) + { + sink.Wait(); + } + + sink.Error?.Throw(); + } + + public virtual void ForEach(IObservable source, Action onNext) + { + using var sink = new ForEach.ObserverIndexed(onNext); + + using (source.SubscribeSafe(sink)) + { + sink.Wait(); + } + + sink.Error?.Throw(); + } + + #endregion + + #region + GetEnumerator + + + public virtual IEnumerator GetEnumerator(IObservable source) + { + var e = new GetEnumerator(); + return e.Run(source); + } + + #endregion + + #region Last + + public virtual TSource Last(IObservable source) + { + return LastOrDefaultInternal(source, throwOnEmpty: true)!; + } + + public virtual TSource Last(IObservable source, Func predicate) + { + return Last(Where(source, predicate)); + } + + #endregion + + #region LastOrDefault + + [return: MaybeNull] + public virtual TSource LastOrDefault(IObservable source) + { + return LastOrDefaultInternal(source, throwOnEmpty: false); + } + + [return: MaybeNull] + public virtual TSource LastOrDefault(IObservable source, Func predicate) + { + return LastOrDefault(Where(source, predicate)); + } + + [return: MaybeNull] + private static TSource LastOrDefaultInternal(IObservable source, bool throwOnEmpty) + { + using var consumer = new LastBlocking(); + + using (source.Subscribe(consumer)) + { + consumer.Wait(); + } + + consumer._error?.Throw(); + + if (throwOnEmpty && !consumer._hasValue) + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + + return consumer._value; + } + + #endregion + + #region + Latest + + + public virtual IEnumerable Latest(IObservable source) + { + return new Latest(source); + } + + #endregion + + #region + MostRecent + + + public virtual IEnumerable MostRecent(IObservable source, TSource initialValue) + { + return new MostRecent(source, initialValue); + } + + #endregion + + #region + Next + + + public virtual IEnumerable Next(IObservable source) + { + return new Next(source); + } + + #endregion + + #region Single + + public virtual TSource Single(IObservable source) + { + return SingleOrDefaultInternal(source, throwOnEmpty: true)!; + } + + public virtual TSource Single(IObservable source, Func predicate) + { + return Single(Where(source, predicate)); + } + + #endregion + + #region SingleOrDefault + + [return: MaybeNull] + public virtual TSource SingleOrDefault(IObservable source) + { + return SingleOrDefaultInternal(source, throwOnEmpty: false); + } + + [return: MaybeNull] + public virtual TSource SingleOrDefault(IObservable source, Func predicate) + { + return SingleOrDefault(Where(source, predicate)); + } + + [return: MaybeNull] + private static TSource SingleOrDefaultInternal(IObservable source, bool throwOnEmpty) + { + using var consumer = new SingleBlocking(); + + using (source.Subscribe(consumer)) + { + consumer.Wait(); + } + + consumer._error?.Throw(); + + if (consumer._hasMoreThanOneElement) + { + throw new InvalidOperationException(Strings_Linq.MORE_THAN_ONE_ELEMENT); + } + + if (throwOnEmpty && !consumer._hasValue) + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + + return consumer._value; + } + + #endregion + + #region Wait + + public virtual TSource Wait(IObservable source) + { + return LastOrDefaultInternal(source, true)!; + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/QueryLanguage.Concurrency.cs b/LibExternal/System.Reactive/Linq/QueryLanguage.Concurrency.cs new file mode 100644 index 0000000..89daaba --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QueryLanguage.Concurrency.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Threading; + +namespace System.Reactive.Linq +{ + internal partial class QueryLanguage + { + #region + ObserveOn + + + public virtual IObservable ObserveOn(IObservable source, IScheduler scheduler) + { + return Synchronization.ObserveOn(source, scheduler); + } + + public virtual IObservable ObserveOn(IObservable source, SynchronizationContext context) + { + return Synchronization.ObserveOn(source, context); + } + + #endregion + + #region + SubscribeOn + + + public virtual IObservable SubscribeOn(IObservable source, IScheduler scheduler) + { + return Synchronization.SubscribeOn(source, scheduler); + } + + public virtual IObservable SubscribeOn(IObservable source, SynchronizationContext context) + { + return Synchronization.SubscribeOn(source, context); + } + + #endregion + + #region + Synchronize + + + public virtual IObservable Synchronize(IObservable source) + { + return Synchronization.Synchronize(source); + } + + public virtual IObservable Synchronize(IObservable source, object gate) + { + return Synchronization.Synchronize(source, gate); + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/QueryLanguage.Conversions.cs b/LibExternal/System.Reactive/Linq/QueryLanguage.Conversions.cs new file mode 100644 index 0000000..6fc470b --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QueryLanguage.Conversions.cs @@ -0,0 +1,109 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Concurrency; + +namespace System.Reactive.Linq +{ + using ObservableImpl; + + internal partial class QueryLanguage + { + #region + Subscribe + + + public virtual IDisposable Subscribe(IEnumerable source, IObserver observer) + { + return Subscribe_(source, observer, SchedulerDefaults.Iteration); + } + + public virtual IDisposable Subscribe(IEnumerable source, IObserver observer, IScheduler scheduler) + { + return Subscribe_(source, observer, scheduler); + } + + private static IDisposable Subscribe_(IEnumerable source, IObserver observer, IScheduler scheduler) + { + var longRunning = scheduler.AsLongRunning(); + if (longRunning != null) + { + // + // [OK] Use of unsafe Subscribe: we're calling into a known producer implementation. + // + return new ToObservableLongRunning(source, longRunning).Subscribe/*Unsafe*/(observer); + } + // + // [OK] Use of unsafe Subscribe: we're calling into a known producer implementation. + // + return new ToObservableRecursive(source, scheduler).Subscribe/*Unsafe*/(observer); + } + + #endregion + + #region + ToEnumerable + + + public virtual IEnumerable ToEnumerable(IObservable source) + { + return new AnonymousEnumerable(() => source.GetEnumerator()); + } + + #endregion + + #region ToEvent + + public virtual IEventSource ToEvent(IObservable source) + { + return new EventSource(source, (h, _) => h(Unit.Default)); + } + + public virtual IEventSource ToEvent(IObservable source) + { + return new EventSource(source, (h, value) => h(value)); + } + + #endregion + + #region ToEventPattern + + public virtual IEventPatternSource ToEventPattern(IObservable> source) + { + return new EventPatternSource( + source, + static (h, evt) => h(evt.Sender, evt.EventArgs) + ); + } + + #endregion + + #region + ToObservable + + + public virtual IObservable ToObservable(IEnumerable source) + { + return ToObservable_(source, SchedulerDefaults.Iteration); + } + + public virtual IObservable ToObservable(IEnumerable source, IScheduler scheduler) + { + return ToObservable_(source, scheduler); + } + + private static IObservable ToObservable_(IEnumerable source, IScheduler scheduler) + { + var longRunning = scheduler.AsLongRunning(); + if (longRunning != null) + { + // + // [OK] Use of unsafe Subscribe: we're calling into a known producer implementation. + // + return new ToObservableLongRunning(source, longRunning); + } + // + // [OK] Use of unsafe Subscribe: we're calling into a known producer implementation. + // + return new ToObservableRecursive(source, scheduler); + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/QueryLanguage.Creation.cs b/LibExternal/System.Reactive/Linq/QueryLanguage.Creation.cs new file mode 100644 index 0000000..ff2e979 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QueryLanguage.Creation.cs @@ -0,0 +1,485 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reactive.Disposables; +using System.Reactive.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Reactive.Linq +{ + using ObservableImpl; + + internal partial class QueryLanguage + { + #region - Create - + + public virtual IObservable Create(Func, IDisposable> subscribe) + { + return new CreateWithDisposableObservable(subscribe); + } + + private sealed class CreateWithDisposableObservable : ObservableBase + { + private readonly Func, IDisposable> _subscribe; + + public CreateWithDisposableObservable(Func, IDisposable> subscribe) + { + _subscribe = subscribe; + } + + protected override IDisposable SubscribeCore(IObserver observer) + { + return _subscribe(observer) ?? Disposable.Empty; + } + } + + public virtual IObservable Create(Func, Action> subscribe) + { + return new CreateWithActionDisposable(subscribe); + } + + private sealed class CreateWithActionDisposable : ObservableBase + { + private readonly Func, Action> _subscribe; + + public CreateWithActionDisposable(Func, Action> subscribe) + { + _subscribe = subscribe; + } + + protected override IDisposable SubscribeCore(IObserver observer) + { + var a = _subscribe(observer); + return a != null ? Disposable.Create(a) : Disposable.Empty; + } + } + + #endregion + + #region - CreateAsync - + + public virtual IObservable Create(Func, CancellationToken, Task> subscribeAsync) + { + return new CreateWithTaskTokenObservable(subscribeAsync); + } + + private sealed class CreateWithTaskTokenObservable : ObservableBase + { + private sealed class Subscription : IDisposable + { + private sealed class TaskCompletionObserver : IObserver + { + private readonly IObserver _observer; + + public TaskCompletionObserver(IObserver observer) + { + _observer = observer; + } + + public void OnCompleted() + { + _observer.OnCompleted(); + } + + public void OnError(Exception error) + { + _observer.OnError(error); + } + + public void OnNext(Unit value) + { + // deliberately ignored + } + } + + private readonly IDisposable _subscription; + private readonly CancellationTokenSource _cts = new(); + + public Subscription(Func, CancellationToken, Task> subscribeAsync, IObserver observer) + { + _subscription = subscribeAsync(observer, _cts.Token) + .Subscribe(new TaskCompletionObserver(observer)); + } + + public void Dispose() + { + _cts.Cancel(); + _subscription.Dispose(); + } + } + + private readonly Func, CancellationToken, Task> _subscribeAsync; + + public CreateWithTaskTokenObservable(Func, CancellationToken, Task> subscribeAsync) + { + _subscribeAsync = subscribeAsync; + } + + protected override IDisposable SubscribeCore(IObserver observer) + { + return new Subscription(_subscribeAsync, observer); + } + } + + public virtual IObservable Create(Func, Task> subscribeAsync) + { + return Create((observer, token) => subscribeAsync(observer)); + } + + public virtual IObservable Create(Func, CancellationToken, Task> subscribeAsync) + { + return new CreateWithTaskDisposable(subscribeAsync); + } + + private sealed class CreateWithTaskDisposable : ObservableBase + { + private sealed class Subscription : IDisposable + { + private sealed class TaskDisposeCompletionObserver : IObserver, IDisposable + { + private readonly IObserver _observer; + private SingleAssignmentDisposableValue _disposable; + + public TaskDisposeCompletionObserver(IObserver observer) + { + _observer = observer; + } + + public void Dispose() + { + _disposable.Dispose(); + } + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + _observer.OnError(error); + } + + public void OnNext(IDisposable value) + { + _disposable.Disposable = value; + } + } + + private readonly TaskDisposeCompletionObserver _observer; + private readonly CancellationTokenSource _cts = new(); + + public Subscription(Func, CancellationToken, Task> subscribeAsync, IObserver observer) + { + // + // We don't cancel the subscription below *ever* and want to make sure the returned resource gets disposed eventually. + // Notice because we're using the AnonymousObservable type, we get auto-detach behavior for free. + // + subscribeAsync(observer, _cts.Token) + .Subscribe(_observer = new TaskDisposeCompletionObserver(observer)); + } + + public void Dispose() + { + _cts.Cancel(); + _observer.Dispose(); + } + } + + private readonly Func, CancellationToken, Task> _subscribeAsync; + + public CreateWithTaskDisposable(Func, CancellationToken, Task> subscribeAsync) + { + _subscribeAsync = subscribeAsync; + } + + protected override IDisposable SubscribeCore(IObserver observer) + { + return new Subscription(_subscribeAsync, observer); + } + } + + public virtual IObservable Create(Func, Task> subscribeAsync) + { + return Create((observer, token) => subscribeAsync(observer)); + } + + public virtual IObservable Create(Func, CancellationToken, Task> subscribeAsync) + { + return new CreateWithTaskActionObservable(subscribeAsync); + } + + private sealed class CreateWithTaskActionObservable : ObservableBase + { + private sealed class Subscription : IDisposable + { + private sealed class TaskDisposeCompletionObserver : IObserver, IDisposable + { + private readonly IObserver _observer; + private Action? _disposable; + + public TaskDisposeCompletionObserver(IObserver observer) + { + _observer = observer; + } + + public void Dispose() + { + Interlocked.Exchange(ref _disposable, Stubs.Nop)?.Invoke(); + } + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + _observer.OnError(error); + } + + public void OnNext(Action value) + { + if (Interlocked.CompareExchange(ref _disposable, value, null) != null) + { + value?.Invoke(); + } + } + } + + private readonly TaskDisposeCompletionObserver _observer; + private readonly CancellationTokenSource _cts = new(); + + public Subscription(Func, CancellationToken, Task> subscribeAsync, IObserver observer) + { + // + // We don't cancel the subscription below *ever* and want to make sure the returned resource gets disposed eventually. + // Notice because we're using the AnonymousObservable type, we get auto-detach behavior for free. + // + subscribeAsync(observer, _cts.Token) + .Subscribe(_observer = new TaskDisposeCompletionObserver(observer)); + } + + public void Dispose() + { + _cts.Cancel(); + _observer.Dispose(); + } + } + + private readonly Func, CancellationToken, Task> _subscribeAsync; + + public CreateWithTaskActionObservable(Func, CancellationToken, Task> subscribeAsync) + { + _subscribeAsync = subscribeAsync; + } + + protected override IDisposable SubscribeCore(IObserver observer) + { + return new Subscription(_subscribeAsync, observer); + } + } + + public virtual IObservable Create(Func, Task> subscribeAsync) + { + return Create((observer, token) => subscribeAsync(observer)); + } + + #endregion + + #region + Defer + + + public virtual IObservable Defer(Func> observableFactory) + { + return new Defer(observableFactory); + } + + #endregion + + #region + DeferAsync + + + public virtual IObservable Defer(Func>> observableFactoryAsync, bool ignoreExceptionsAfterUnsubscribe) + { + return Defer(() => StartAsync(observableFactoryAsync, new TaskObservationOptions.Value(null, ignoreExceptionsAfterUnsubscribe)).Merge()); + } + + public virtual IObservable Defer(Func>> observableFactoryAsync, bool ignoreExceptionsAfterUnsubscribe) + { + return Defer(() => StartAsync(observableFactoryAsync, new TaskObservationOptions.Value(null, ignoreExceptionsAfterUnsubscribe)).Merge()); + } + + #endregion + + #region + Empty + + + public virtual IObservable Empty() + { + return EmptyDirect.Instance; + } + + public virtual IObservable Empty(IScheduler scheduler) + { + return new Empty(scheduler); + } + + #endregion + + #region + Generate + + + public virtual IObservable Generate(TState initialState, Func condition, Func iterate, Func resultSelector) + { + return new Generate.NoTime(initialState, condition, iterate, resultSelector, SchedulerDefaults.Iteration); + } + + public virtual IObservable Generate(TState initialState, Func condition, Func iterate, Func resultSelector, IScheduler scheduler) + { + return new Generate.NoTime(initialState, condition, iterate, resultSelector, scheduler); + } + + #endregion + + #region + Never + + + public virtual IObservable Never() + { + return ObservableImpl.Never.Default; + } + + #endregion + + #region + Range + + + public virtual IObservable Range(int start, int count) + { + return Range_(start, count, SchedulerDefaults.Iteration); + } + + public virtual IObservable Range(int start, int count, IScheduler scheduler) + { + return Range_(start, count, scheduler); + } + + private static IObservable Range_(int start, int count, IScheduler scheduler) + { + var longRunning = scheduler.AsLongRunning(); + if (longRunning != null) + { + return new RangeLongRunning(start, count, longRunning); + } + return new RangeRecursive(start, count, scheduler); + } + + #endregion + + #region + Repeat + + + public virtual IObservable Repeat(TResult value) + { + return Repeat_(value, SchedulerDefaults.Iteration); + } + + public virtual IObservable Repeat(TResult value, IScheduler scheduler) + { + return Repeat_(value, scheduler); + } + + private static IObservable Repeat_(TResult value, IScheduler scheduler) + { + var longRunning = scheduler.AsLongRunning(); + if (longRunning != null) + { + return new Repeat.ForeverLongRunning(value, longRunning); + } + return new Repeat.ForeverRecursive(value, scheduler); + } + + public virtual IObservable Repeat(TResult value, int repeatCount) + { + return Repeat_(value, repeatCount, SchedulerDefaults.Iteration); + } + + public virtual IObservable Repeat(TResult value, int repeatCount, IScheduler scheduler) + { + return Repeat_(value, repeatCount, scheduler); + } + + private static IObservable Repeat_(TResult value, int repeatCount, IScheduler scheduler) + { + var longRunning = scheduler.AsLongRunning(); + if (longRunning != null) + { + return new Repeat.CountLongRunning(value, repeatCount, longRunning); + } + return new Repeat.CountRecursive(value, repeatCount, scheduler); + } + + #endregion + + #region + Return + + + public virtual IObservable Return(TResult value) + { + // ConstantTimeOperations is a mutable field so we'd have to + // check if it points to the immediate scheduler instance + // which is done in the other Return overload anyway + return Return(value, SchedulerDefaults.ConstantTimeOperations); + } + + public virtual IObservable Return(TResult value, IScheduler scheduler) + { + if (scheduler == ImmediateScheduler.Instance) + { + return new ReturnImmediate(value); + } + return new Return(value, scheduler); + } + + #endregion + + #region + Throw + + + public virtual IObservable Throw(Exception exception) + { + // ConstantTimeOperations is a mutable field so we'd have to + // check if it points to the immediate scheduler instance + // which is done in the other Return overload anyway + return Throw(exception, SchedulerDefaults.ConstantTimeOperations); + } + + public virtual IObservable Throw(Exception exception, IScheduler scheduler) + { + if (scheduler == ImmediateScheduler.Instance) + { + return new ThrowImmediate(exception); + } + return new Throw(exception, scheduler); + } + + #endregion + + #region + Using + + + public virtual IObservable Using(Func resourceFactory, Func> observableFactory) where TResource : IDisposable + { + return new Using(resourceFactory, observableFactory); + } + + #endregion + + #region - UsingAsync - + + public virtual IObservable Using(Func> resourceFactoryAsync, Func>> observableFactoryAsync) where TResource : IDisposable + { + return Observable.FromAsync(resourceFactoryAsync) + .SelectMany(resource => + Observable.Using( + () => resource, + resource_ => Observable.FromAsync(ct => observableFactoryAsync(resource_, ct)).Merge() + ) + ); + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/QueryLanguage.Events.cs b/LibExternal/System.Reactive/Linq/QueryLanguage.Events.cs new file mode 100644 index 0000000..c9354c9 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QueryLanguage.Events.cs @@ -0,0 +1,426 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Threading; + +namespace System.Reactive.Linq +{ + using System.Diagnostics.CodeAnalysis; + + using ObservableImpl; + + // + // BREAKING CHANGE v2 > v1.x - FromEvent[Pattern] now has an implicit SubscribeOn and Publish operation. + // + // See FromEvent.cs for more information. + // + internal partial class QueryLanguage + { + #region + FromEventPattern + + + #region Strongly typed + + #region Action + + public virtual IObservable> FromEventPattern(Action addHandler, Action removeHandler) + { + return FromEventPattern_(addHandler, removeHandler, GetSchedulerForCurrentContext()); + } + + public virtual IObservable> FromEventPattern(Action addHandler, Action removeHandler, IScheduler scheduler) + { + return FromEventPattern_(addHandler, removeHandler, scheduler); + } + + #region Implementation + + private static IObservable> FromEventPattern_(Action addHandler, Action removeHandler, IScheduler scheduler) + { + return new FromEventPattern.Impl(e => new EventHandler(e), addHandler, removeHandler, scheduler); + } + + #endregion + + #endregion + + #region Action + + public virtual IObservable> FromEventPattern(Action addHandler, Action removeHandler) + { + return FromEventPattern_(addHandler, removeHandler, GetSchedulerForCurrentContext()); + } + + public virtual IObservable> FromEventPattern(Action addHandler, Action removeHandler, IScheduler scheduler) + { + return FromEventPattern_(addHandler, removeHandler, scheduler); + } + + #region Implementation + + private static IObservable> FromEventPattern_(Action addHandler, Action removeHandler, IScheduler scheduler) + { + return new FromEventPattern.Impl(addHandler, removeHandler, scheduler); + } + + #endregion + + public virtual IObservable> FromEventPattern(Func, TDelegate> conversion, Action addHandler, Action removeHandler) + { + return FromEventPattern_(conversion, addHandler, removeHandler, GetSchedulerForCurrentContext()); + } + + public virtual IObservable> FromEventPattern(Func, TDelegate> conversion, Action addHandler, Action removeHandler, IScheduler scheduler) + { + return FromEventPattern_(conversion, addHandler, removeHandler, scheduler); + } + + #region Implementation + + private static IObservable> FromEventPattern_(Func, TDelegate> conversion, Action addHandler, Action removeHandler, IScheduler scheduler) + { + return new FromEventPattern.Impl(conversion, addHandler, removeHandler, scheduler); + } + + #endregion + + public virtual IObservable> FromEventPattern(Action addHandler, Action removeHandler) + { + return FromEventPattern_(addHandler, removeHandler, GetSchedulerForCurrentContext()); + } + + public virtual IObservable> FromEventPattern(Action addHandler, Action removeHandler, IScheduler scheduler) + { + return FromEventPattern_(addHandler, removeHandler, scheduler); + } + + #region Implementation + + private static IObservable> FromEventPattern_(Action addHandler, Action removeHandler, IScheduler scheduler) + { + return new FromEventPattern.Impl(addHandler, removeHandler, scheduler); + } + + #endregion + + #endregion + + #region Action> + + public virtual IObservable> FromEventPattern(Action> addHandler, Action> removeHandler) + { + return FromEventPattern_(addHandler, removeHandler, GetSchedulerForCurrentContext()); + } + + public virtual IObservable> FromEventPattern(Action> addHandler, Action> removeHandler, IScheduler scheduler) + { + return FromEventPattern_(addHandler, removeHandler, scheduler); + } + + #region Implementation + + private static IObservable> FromEventPattern_(Action> addHandler, Action> removeHandler, IScheduler scheduler) + { + return new FromEventPattern.Impl, TEventArgs>(handler => handler, addHandler, removeHandler, scheduler); + } + + #endregion + + #endregion + + #endregion + + #region Reflection + + #region Instance events + +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public virtual IObservable> FromEventPattern(object target, string eventName) + { + return FromEventPattern_(target, eventName, GetSchedulerForCurrentContext()); + } + +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public virtual IObservable> FromEventPattern(object target, string eventName, IScheduler scheduler) + { + return FromEventPattern_(target, eventName, scheduler); + } + + #region Implementation + +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + private static IObservable> FromEventPattern_(object target, string eventName, IScheduler scheduler) + { + return FromEventPattern_>(target.GetType(), target, eventName, (sender, args) => new EventPattern(sender, args), scheduler); + } + + #endregion + +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public virtual IObservable> FromEventPattern(object target, string eventName) + { + return FromEventPattern_(target, eventName, GetSchedulerForCurrentContext()); + } + +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public virtual IObservable> FromEventPattern(object target, string eventName, IScheduler scheduler) + { + return FromEventPattern_(target, eventName, scheduler); + } + + #region Implementation + +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + private static IObservable> FromEventPattern_(object target, string eventName, IScheduler scheduler) + { + return FromEventPattern_>(target.GetType(), target, eventName, (sender, args) => new EventPattern(sender, args), scheduler); + } + + #endregion + +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public virtual IObservable> FromEventPattern(object target, string eventName) + { + return FromEventPattern_(target, eventName, GetSchedulerForCurrentContext()); + } + +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public virtual IObservable> FromEventPattern(object target, string eventName, IScheduler scheduler) + { + return FromEventPattern_(target, eventName, scheduler); + } + + #region Implementation + +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + private static IObservable> FromEventPattern_(object target, string eventName, IScheduler scheduler) + { + return FromEventPattern_>(target.GetType(), target, eventName, (sender, args) => new EventPattern(sender, args), scheduler); + } + + #endregion + + #endregion + + #region Static events + +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public virtual IObservable> FromEventPattern(Type type, string eventName) + { + return FromEventPattern_(type, eventName, GetSchedulerForCurrentContext()); + } + +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public virtual IObservable> FromEventPattern(Type type, string eventName, IScheduler scheduler) + { + return FromEventPattern_(type, eventName, scheduler); + } + + #region Implementation + +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + private static IObservable> FromEventPattern_(Type type, string eventName, IScheduler scheduler) + { + return FromEventPattern_>(type, null, eventName, (sender, args) => new EventPattern(sender, args), scheduler); + } + + #endregion + +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public virtual IObservable> FromEventPattern(Type type, string eventName) + { + return FromEventPattern_(type, eventName, GetSchedulerForCurrentContext()); + } + +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public virtual IObservable> FromEventPattern(Type type, string eventName, IScheduler scheduler) + { + return FromEventPattern_(type, eventName, scheduler); + } + + #region Implementation + +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + private static IObservable> FromEventPattern_(Type type, string eventName, IScheduler scheduler) + { + return FromEventPattern_>(type, null, eventName, (sender, args) => new EventPattern(sender, args), scheduler); + } + + #endregion + +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public virtual IObservable> FromEventPattern(Type type, string eventName) + { + return FromEventPattern_(type, eventName, GetSchedulerForCurrentContext()); + } + +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + public virtual IObservable> FromEventPattern(Type type, string eventName, IScheduler scheduler) + { + return FromEventPattern_(type, eventName, scheduler); + } + + #region Implementation + +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + private static IObservable> FromEventPattern_(Type type, string eventName, IScheduler scheduler) + { + return FromEventPattern_>(type, null, eventName, static (sender, args) => new EventPattern(sender, args), scheduler); + } + + #endregion + + #endregion + + #region Helper methods + +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.EventReflectionTrimIncompatibilityMessage)] +#endif + private static IObservable FromEventPattern_(Type targetType, object? target, string eventName, Func getResult, IScheduler scheduler) + { + ReflectionUtils.GetEventMethods(targetType, target, eventName, out var addMethod, out var removeMethod, out var delegateType, out var isWinRT); + + return new FromEventPattern.Handler(target, delegateType, addMethod, removeMethod, getResult, isWinRT, scheduler); + } + + #endregion + + #endregion + + #endregion + + #region FromEvent + + public virtual IObservable FromEvent(Func, TDelegate> conversion, Action addHandler, Action removeHandler) + { + return FromEvent_(conversion, addHandler, removeHandler, GetSchedulerForCurrentContext()); + } + + public virtual IObservable FromEvent(Func, TDelegate> conversion, Action addHandler, Action removeHandler, IScheduler scheduler) + { + return FromEvent_(conversion, addHandler, removeHandler, scheduler); + } + + #region Implementation + + private static IObservable FromEvent_(Func, TDelegate> conversion, Action addHandler, Action removeHandler, IScheduler scheduler) + { + return new FromEvent(conversion, addHandler, removeHandler, scheduler); + } + + #endregion + + public virtual IObservable FromEvent(Action addHandler, Action removeHandler) + { + return FromEvent_(addHandler, removeHandler, GetSchedulerForCurrentContext()); + } + + public virtual IObservable FromEvent(Action addHandler, Action removeHandler, IScheduler scheduler) + { + return FromEvent_(addHandler, removeHandler, scheduler); + } + + #region Implementation + + private static IObservable FromEvent_(Action addHandler, Action removeHandler, IScheduler scheduler) + { + return new FromEvent(addHandler, removeHandler, scheduler); + } + + #endregion + + public virtual IObservable FromEvent(Action> addHandler, Action> removeHandler) + { + return FromEvent_(addHandler, removeHandler, GetSchedulerForCurrentContext()); + } + + public virtual IObservable FromEvent(Action> addHandler, Action> removeHandler, IScheduler scheduler) + { + return FromEvent_(addHandler, removeHandler, scheduler); + } + + #region Implementation + + private static IObservable FromEvent_(Action> addHandler, Action> removeHandler, IScheduler scheduler) + { + return new FromEvent, TEventArgs>(static h => h, addHandler, removeHandler, scheduler); + } + + #endregion + + public virtual IObservable FromEvent(Action addHandler, Action removeHandler) + { + return FromEvent_(addHandler, removeHandler, GetSchedulerForCurrentContext()); + } + + public virtual IObservable FromEvent(Action addHandler, Action removeHandler, IScheduler scheduler) + { + return FromEvent_(addHandler, removeHandler, scheduler); + } + + #region Implementation + + private static IObservable FromEvent_(Action addHandler, Action removeHandler, IScheduler scheduler) + { + return new FromEvent(static h => new Action(() => h(new Unit())), addHandler, removeHandler, scheduler); + } + + #endregion + + #endregion + + #region Helpers + + private static IScheduler GetSchedulerForCurrentContext() + { + var context = SynchronizationContext.Current; + + if (context != null) + { + return new SynchronizationContextScheduler(context, alwaysPost: false); + } + + return SchedulerDefaults.ConstantTimeOperations; + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/QueryLanguage.Imperative.cs b/LibExternal/System.Reactive/Linq/QueryLanguage.Imperative.cs new file mode 100644 index 0000000..1867d48 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QueryLanguage.Imperative.cs @@ -0,0 +1,196 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Concurrency; +using System.Reactive.Disposables; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Reactive.Linq +{ + using ObservableImpl; + + internal partial class QueryLanguage + { + #region ForEachAsync + + public virtual Task ForEachAsync(IObservable source, Action onNext) + { + return ForEachAsync_(source, onNext, CancellationToken.None); + } + + public virtual Task ForEachAsync(IObservable source, Action onNext, CancellationToken cancellationToken) + { + return ForEachAsync_(source, onNext, cancellationToken); + } + + public virtual Task ForEachAsync(IObservable source, Action onNext) + { + var i = 0; + return ForEachAsync_(source, x => onNext(x, checked(i++)), CancellationToken.None); + } + + public virtual Task ForEachAsync(IObservable source, Action onNext, CancellationToken cancellationToken) + { + var i = 0; + return ForEachAsync_(source, x => onNext(x, checked(i++)), cancellationToken); + } + + private static Task ForEachAsync_(IObservable source, Action onNext, CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); + var subscription = new SingleAssignmentDisposable(); + + var ctr = default(CancellationTokenRegistration); + + if (cancellationToken.CanBeCanceled) + { + ctr = cancellationToken.Register(() => + { + tcs.TrySetCanceled(cancellationToken); + subscription.Dispose(); + }); + } + + if (!cancellationToken.IsCancellationRequested) + { + // Making sure we always complete, even if disposing throws. + var dispose = new Action(action => + { + try + { + ctr.Dispose(); // no null-check needed (struct) + subscription.Dispose(); + } + catch (Exception ex) + { + tcs.TrySetException(ex); + return; + } + + action(); + }); + + var taskCompletionObserver = new AnonymousObserver( + x => + { + if (!subscription.IsDisposed) + { + try + { + onNext(x); + } + catch (Exception exception) + { + dispose(() => tcs.TrySetException(exception)); + } + } + }, + exception => + { + dispose(() => tcs.TrySetException(exception)); + }, + () => + { + dispose(() => tcs.TrySetResult(null)); + } + ); + + // + // Subtle race condition: if the source completes before we reach the line below, the SingleAssigmentDisposable + // will already have been disposed. Upon assignment, the disposable resource being set will be disposed on the + // spot, which may throw an exception. (See TFS 487142) + // + try + { + // + // [OK] Use of unsafe Subscribe: we're catching the exception here to set the TaskCompletionSource. + // + // Notice we could use a safe subscription to route errors through OnError, but we still need the + // exception handling logic here for the reason explained above. We cannot afford to throw here + // and as a result never set the TaskCompletionSource, so we tunnel everything through here. + // + subscription.Disposable = source.Subscribe/*Unsafe*/(taskCompletionObserver); + } + catch (Exception ex) + { + tcs.TrySetException(ex); + } + } + + return tcs.Task; + } + + #endregion + + #region + Case + + + public virtual IObservable Case(Func selector, IDictionary> sources) + where TValue : notnull + { + return Case(selector, sources, Empty()); + } + + public virtual IObservable Case(Func selector, IDictionary> sources, IScheduler scheduler) + where TValue : notnull + { + return Case(selector, sources, Empty(scheduler)); + } + + public virtual IObservable Case(Func selector, IDictionary> sources, IObservable defaultSource) + where TValue : notnull + { + return new Case(selector, sources, defaultSource); + } + + #endregion + + #region + DoWhile + + + public virtual IObservable DoWhile(IObservable source, Func condition) + { + return new DoWhile(source, condition); + } + + #endregion + + #region + For + + + public virtual IObservable For(IEnumerable source, Func> resultSelector) + { + return new For(source, resultSelector); + } + + #endregion + + #region + If + + + public virtual IObservable If(Func condition, IObservable thenSource) + { + return If(condition, thenSource, Empty()); + } + + public virtual IObservable If(Func condition, IObservable thenSource, IScheduler scheduler) + { + return If(condition, thenSource, Empty(scheduler)); + } + + public virtual IObservable If(Func condition, IObservable thenSource, IObservable elseSource) + { + return new If(condition, thenSource, elseSource); + } + + #endregion + + #region + While + + + public virtual IObservable While(Func condition, IObservable source) + { + return new While(condition, source); + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/QueryLanguage.Joins.cs b/LibExternal/System.Reactive/Linq/QueryLanguage.Joins.cs new file mode 100644 index 0000000..8794e33 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QueryLanguage.Joins.cs @@ -0,0 +1,127 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Disposables; +using System.Reactive.Joins; + +namespace System.Reactive.Linq +{ + internal partial class QueryLanguage + { + #region And + + public virtual Pattern And(IObservable left, IObservable right) + { + return new Pattern(left, right); + } + + #endregion + + #region Then + + public virtual Plan Then(IObservable source, Func selector) + { + return new Pattern(source).Then(selector); + } + + #endregion + + #region When + + public virtual IObservable When(params Plan[] plans) + { + return When((IEnumerable>)plans); + } + + public virtual IObservable When(IEnumerable> plans) + { + return new WhenObservable(plans); + } + + private sealed class WhenObservable : ObservableBase + { + private readonly IEnumerable> _plans; + + public WhenObservable(IEnumerable> plans) + { + _plans = plans; + } + + protected override IDisposable SubscribeCore(IObserver observer) + { + var externalSubscriptions = new Dictionary(); + var gate = new object(); + var activePlans = new List(); + var outObserver = new OutObserver(observer, externalSubscriptions); + try + { + void onDeactivate(ActivePlan activePlan) + { + activePlans.Remove(activePlan); + if (activePlans.Count == 0) + { + outObserver.OnCompleted(); + } + } + + foreach (var plan in _plans) + { + activePlans.Add(plan.Activate(externalSubscriptions, outObserver, + onDeactivate)); + } + } + catch (Exception e) + { + // + // [OK] Use of unsafe Subscribe: we're calling into a known producer implementation. + // + observer.OnError(e); + return Disposable.Empty; + } + + var group = new CompositeDisposable(externalSubscriptions.Values.Count); + foreach (var joinObserver in externalSubscriptions.Values) + { + joinObserver.Subscribe(gate); + group.Add(joinObserver); + } + return group; + } + + private sealed class OutObserver : IObserver + { + private readonly IObserver _observer; + private readonly Dictionary _externalSubscriptions; + + public OutObserver(IObserver observer, Dictionary externalSubscriptions) + { + _observer = observer; + _externalSubscriptions = externalSubscriptions; + } + + public void OnCompleted() + { + _observer.OnCompleted(); + } + + public void OnError(Exception exception) + { + foreach (var po in _externalSubscriptions.Values) + { + po.Dispose(); + } + _observer.OnError(exception); + } + + public void OnNext(TResult value) + { + _observer.OnNext(value); + } + } + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/QueryLanguage.Multiple.CombineLatest.cs b/LibExternal/System.Reactive/Linq/QueryLanguage.Multiple.CombineLatest.cs new file mode 100644 index 0000000..fe204bb --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QueryLanguage.Multiple.CombineLatest.cs @@ -0,0 +1,123 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +// This code was generated by a T4 template at 10/05/2020 14:25:15. + +namespace System.Reactive.Linq +{ + using ObservableImpl; + + internal partial class QueryLanguage + { + public virtual IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, Func resultSelector) + { + return new CombineLatest(source1, source2, source3, resultSelector); + } + + public virtual IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, Func resultSelector) + { + return new CombineLatest(source1, source2, source3, source4, resultSelector); + } + + public virtual IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, Func resultSelector) + { + return new CombineLatest(source1, source2, source3, source4, source5, resultSelector); + } + + public virtual IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, Func resultSelector) + { + return new CombineLatest(source1, source2, source3, source4, source5, source6, resultSelector); + } + + public virtual IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, Func resultSelector) + { + return new CombineLatest(source1, source2, source3, source4, source5, source6, source7, resultSelector); + } + + public virtual IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, Func resultSelector) + { + return new CombineLatest(source1, source2, source3, source4, source5, source6, source7, source8, resultSelector); + } + + public virtual IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, Func resultSelector) + { + return new CombineLatest(source1, source2, source3, source4, source5, source6, source7, source8, source9, resultSelector); + } + + public virtual IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, Func resultSelector) + { + return new CombineLatest(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, resultSelector); + } + + public virtual IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, Func resultSelector) + { + return new CombineLatest(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, source11, resultSelector); + } + + public virtual IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, Func resultSelector) + { + return new CombineLatest(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, source11, source12, resultSelector); + } + + public virtual IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, Func resultSelector) + { + return new CombineLatest(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, source11, source12, source13, resultSelector); + } + + public virtual IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, Func resultSelector) + { + return new CombineLatest(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, source11, source12, source13, source14, resultSelector); + } + + public virtual IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, IObservable source15, Func resultSelector) + { + return new CombineLatest(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, source11, source12, source13, source14, source15, resultSelector); + } + + public virtual IObservable CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, IObservable source15, IObservable source16, Func resultSelector) + { + return new CombineLatest(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, source11, source12, source13, source14, source15, source16, resultSelector); + } + + } + + internal partial class QueryLanguageEx + { + public virtual IObservable<(TSource1, TSource2)> CombineLatest(IObservable source1, IObservable source2) + { + return new CombineLatest(source1, source2, (t1, t2) => (t1, t2)); + } + + public virtual IObservable<(TSource1, TSource2, TSource3)> CombineLatest(IObservable source1, IObservable source2, IObservable source3) + { + return new CombineLatest(source1, source2, source3, (t1, t2, t3) => (t1, t2, t3)); + } + + public virtual IObservable<(TSource1, TSource2, TSource3, TSource4)> CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4) + { + return new CombineLatest(source1, source2, source3, source4, (t1, t2, t3, t4) => (t1, t2, t3, t4)); + } + + public virtual IObservable<(TSource1, TSource2, TSource3, TSource4, TSource5)> CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5) + { + return new CombineLatest(source1, source2, source3, source4, source5, (t1, t2, t3, t4, t5) => (t1, t2, t3, t4, t5)); + } + + public virtual IObservable<(TSource1, TSource2, TSource3, TSource4, TSource5, TSource6)> CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6) + { + return new CombineLatest(source1, source2, source3, source4, source5, source6, (t1, t2, t3, t4, t5, t6) => (t1, t2, t3, t4, t5, t6)); + } + + public virtual IObservable<(TSource1, TSource2, TSource3, TSource4, TSource5, TSource6, TSource7)> CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7) + { + return new CombineLatest(source1, source2, source3, source4, source5, source6, source7, (t1, t2, t3, t4, t5, t6, t7) => (t1, t2, t3, t4, t5, t6, t7)); + } + + public virtual IObservable<(TSource1, TSource2, TSource3, TSource4, TSource5, TSource6, TSource7, TSource8)> CombineLatest(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8) + { + return new CombineLatest(source1, source2, source3, source4, source5, source6, source7, source8, (t1, t2, t3, t4, t5, t6, t7, t8) => (t1, t2, t3, t4, t5, t6, t7, t8)); + } + + } +} diff --git a/LibExternal/System.Reactive/Linq/QueryLanguage.Multiple.CombineLatest.tt b/LibExternal/System.Reactive/Linq/QueryLanguage.Multiple.CombineLatest.tt new file mode 100644 index 0000000..b7f2016 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QueryLanguage.Multiple.CombineLatest.tt @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> +// This code was generated by a T4 template at <#=DateTime.Now#>. + +namespace System.Reactive.Linq +{ + using ObservableImpl; + + internal partial class QueryLanguage + { +<# +for (int i = 3; i <= 16; i++) +{ + var parameters = string.Join(", ", Enumerable.Range(1, i).Select(j => "IObservable source" + j)); + var genArgs = string.Join(", ", Enumerable.Range(1, i).Select(j => "TSource" + j)); + var sources = string.Join(", ", Enumerable.Range(1, i).Select(j => "source" + j)); + +#> + public virtual IObservable CombineLatest<<#=genArgs#>, TResult>(<#=parameters#>, Func<<#=genArgs#>, TResult> resultSelector) + { + return new CombineLatest<<#=genArgs#>, TResult>(<#=sources#>, resultSelector); + } + +<# +} +#> + } + + internal partial class QueryLanguageEx + { +<# +for (int i = 2; i <= 8; i++) +{ + var parameters = string.Join(", ", Enumerable.Range(1, i).Select(j => "IObservable source" + j)); + var genArgs = string.Join(", ", Enumerable.Range(1, i).Select(j => "TSource" + j)); + var sources = string.Join(", ", Enumerable.Range(1, i).Select(j => "source" + j)); + var tuple = "(" + string.Join(", ", Enumerable.Range(1, i).Select(j => "TSource" + j)) + ")"; + var vals = string.Join(", ", Enumerable.Range(1, i).Select(j => "t" + j)); + var selector = "(" + vals + ") => (" + vals + ")"; + +#> + public virtual IObservable<<#=tuple#>> CombineLatest<<#=genArgs#>>(<#=parameters#>) + { + return new CombineLatest<<#=genArgs#>, <#=tuple#>>(<#=sources#>, <#=selector#>); + } + +<# +} +#> + } +} diff --git a/LibExternal/System.Reactive/Linq/QueryLanguage.Multiple.Zip.cs b/LibExternal/System.Reactive/Linq/QueryLanguage.Multiple.Zip.cs new file mode 100644 index 0000000..1adad4f --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QueryLanguage.Multiple.Zip.cs @@ -0,0 +1,124 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +// This code was generated by a T4 template at 10/05/2020 14:25:21. + +namespace System.Reactive.Linq +{ + using ObservableImpl; + + internal partial class QueryLanguage + { + public virtual IObservable Zip(IObservable source1, IObservable source2, IObservable source3, Func resultSelector) + { + return new Zip(source1, source2, source3, resultSelector); + } + + public virtual IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, Func resultSelector) + { + return new Zip(source1, source2, source3, source4, resultSelector); + } + + public virtual IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, Func resultSelector) + { + return new Zip(source1, source2, source3, source4, source5, resultSelector); + } + + public virtual IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, Func resultSelector) + { + return new Zip(source1, source2, source3, source4, source5, source6, resultSelector); + } + + public virtual IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, Func resultSelector) + { + return new Zip(source1, source2, source3, source4, source5, source6, source7, resultSelector); + } + + public virtual IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, Func resultSelector) + { + return new Zip(source1, source2, source3, source4, source5, source6, source7, source8, resultSelector); + } + + public virtual IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, Func resultSelector) + { + return new Zip(source1, source2, source3, source4, source5, source6, source7, source8, source9, resultSelector); + } + + public virtual IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, Func resultSelector) + { + return new Zip(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, resultSelector); + } + + public virtual IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, Func resultSelector) + { + return new Zip(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, source11, resultSelector); + } + + public virtual IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, Func resultSelector) + { + return new Zip(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, source11, source12, resultSelector); + } + + public virtual IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, Func resultSelector) + { + return new Zip(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, source11, source12, source13, resultSelector); + } + + public virtual IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, Func resultSelector) + { + return new Zip(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, source11, source12, source13, source14, resultSelector); + } + + public virtual IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, IObservable source15, Func resultSelector) + { + return new Zip(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, source11, source12, source13, source14, source15, resultSelector); + } + + public virtual IObservable Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8, IObservable source9, IObservable source10, IObservable source11, IObservable source12, IObservable source13, IObservable source14, IObservable source15, IObservable source16, Func resultSelector) + { + return new Zip(source1, source2, source3, source4, source5, source6, source7, source8, source9, source10, source11, source12, source13, source14, source15, source16, resultSelector); + } + + } + + + internal partial class QueryLanguageEx + { + public virtual IObservable<(TSource1, TSource2)> Zip(IObservable source1, IObservable source2) + { + return new Zip.Observable(source1, source2, (t1, t2) => (t1, t2)); + } + + public virtual IObservable<(TSource1, TSource2, TSource3)> Zip(IObservable source1, IObservable source2, IObservable source3) + { + return new Zip(source1, source2, source3, (t1, t2, t3) => (t1, t2, t3)); + } + + public virtual IObservable<(TSource1, TSource2, TSource3, TSource4)> Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4) + { + return new Zip(source1, source2, source3, source4, (t1, t2, t3, t4) => (t1, t2, t3, t4)); + } + + public virtual IObservable<(TSource1, TSource2, TSource3, TSource4, TSource5)> Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5) + { + return new Zip(source1, source2, source3, source4, source5, (t1, t2, t3, t4, t5) => (t1, t2, t3, t4, t5)); + } + + public virtual IObservable<(TSource1, TSource2, TSource3, TSource4, TSource5, TSource6)> Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6) + { + return new Zip(source1, source2, source3, source4, source5, source6, (t1, t2, t3, t4, t5, t6) => (t1, t2, t3, t4, t5, t6)); + } + + public virtual IObservable<(TSource1, TSource2, TSource3, TSource4, TSource5, TSource6, TSource7)> Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7) + { + return new Zip(source1, source2, source3, source4, source5, source6, source7, (t1, t2, t3, t4, t5, t6, t7) => (t1, t2, t3, t4, t5, t6, t7)); + } + + public virtual IObservable<(TSource1, TSource2, TSource3, TSource4, TSource5, TSource6, TSource7, TSource8)> Zip(IObservable source1, IObservable source2, IObservable source3, IObservable source4, IObservable source5, IObservable source6, IObservable source7, IObservable source8) + { + return new Zip(source1, source2, source3, source4, source5, source6, source7, source8, (t1, t2, t3, t4, t5, t6, t7, t8) => (t1, t2, t3, t4, t5, t6, t7, t8)); + } + + } +} diff --git a/LibExternal/System.Reactive/Linq/QueryLanguage.Multiple.Zip.tt b/LibExternal/System.Reactive/Linq/QueryLanguage.Multiple.Zip.tt new file mode 100644 index 0000000..81a739b --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QueryLanguage.Multiple.Zip.tt @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> +// This code was generated by a T4 template at <#=DateTime.Now#>. + +namespace System.Reactive.Linq +{ + using ObservableImpl; + + internal partial class QueryLanguage + { +<# +for (int i = 3; i <= 16; i++) +{ + var parameters = string.Join(", ", Enumerable.Range(1, i).Select(j => "IObservable source" + j)); + var genArgs = string.Join(", ", Enumerable.Range(1, i).Select(j => "TSource" + j)); + var sources = string.Join(", ", Enumerable.Range(1, i).Select(j => "source" + j)); + +#> + public virtual IObservable Zip<<#=genArgs#>, TResult>(<#=parameters#>, Func<<#=genArgs#>, TResult> resultSelector) + { + return new Zip<<#=genArgs#>, TResult>(<#=sources#>, resultSelector); + } + +<# +} +#> + } + + + internal partial class QueryLanguageEx + { +<# +for (int i = 2; i <= 8; i++) +{ + var parameters = string.Join(", ", Enumerable.Range(1, i).Select(j => "IObservable source" + j)); + var genArgs = string.Join(", ", Enumerable.Range(1, i).Select(j => "TSource" + j)); + var sources = string.Join(", ", Enumerable.Range(1, i).Select(j => "source" + j)); + var tuple = "(" + string.Join(", ", Enumerable.Range(1, i).Select(j => "TSource" + j)) + ")"; + var vals = string.Join(", ", Enumerable.Range(1, i).Select(j => "t" + j)); + var selector = "(" + vals + ") => (" + vals + ")"; + var extra = i == 2 ? ".Observable" : ""; + +#> + public virtual IObservable<<#=tuple#>> Zip<<#=genArgs#>>(<#=parameters#>) + { + return new Zip<<#=genArgs#>, <#=tuple#>><#=extra#>(<#=sources#>, <#=selector#>); + } + +<# +} +#> + } +} diff --git a/LibExternal/System.Reactive/Linq/QueryLanguage.Multiple.cs b/LibExternal/System.Reactive/Linq/QueryLanguage.Multiple.cs new file mode 100644 index 0000000..4a9d1b6 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QueryLanguage.Multiple.cs @@ -0,0 +1,349 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Concurrency; +using System.Reactive.Threading.Tasks; +using System.Threading.Tasks; + +namespace System.Reactive.Linq +{ + using ObservableImpl; + + internal partial class QueryLanguage + { + #region + Amb + + + public virtual IObservable Amb(IObservable first, IObservable second) + { + return new Amb(first, second); + } + + public virtual IObservable Amb(params IObservable[] sources) + { + return new AmbManyArray(sources); + } + + public virtual IObservable Amb(IEnumerable> sources) + { + return new AmbManyEnumerable(sources); + } + + #endregion + + #region + Buffer + + + public virtual IObservable> Buffer(IObservable source, Func> bufferClosingSelector) + { + return new Buffer.Selector(source, bufferClosingSelector); + } + + public virtual IObservable> Buffer(IObservable source, IObservable bufferOpenings, Func> bufferClosingSelector) + { + return source.Window(bufferOpenings, bufferClosingSelector).SelectMany(ToList); + } + + public virtual IObservable> Buffer(IObservable source, IObservable bufferBoundaries) + { + return new Buffer.Boundaries(source, bufferBoundaries); + } + + #endregion + + #region + Catch + + + public virtual IObservable Catch(IObservable source, Func> handler) where TException : Exception + { + return new Catch(source, handler); + } + + public virtual IObservable Catch(IObservable first, IObservable second) + { + return Catch_(new[] { first, second }); + } + + public virtual IObservable Catch(params IObservable[] sources) + { + return Catch_(sources); + } + + public virtual IObservable Catch(IEnumerable> sources) + { + return Catch_(sources); + } + + private static IObservable Catch_(IEnumerable> sources) + { + return new Catch(sources); + } + + #endregion + + #region + CombineLatest + + + public virtual IObservable CombineLatest(IObservable first, IObservable second, Func resultSelector) + { + return new CombineLatest(first, second, resultSelector); + } + + public virtual IObservable CombineLatest(IEnumerable> sources, Func, TResult> resultSelector) + { + return CombineLatest_(sources, resultSelector); + } + + public virtual IObservable> CombineLatest(IEnumerable> sources) + { + return CombineLatest_(sources, res => res.ToList()); + } + + public virtual IObservable> CombineLatest(params IObservable[] sources) + { + return CombineLatest_(sources, res => res.ToList()); + } + + private static IObservable CombineLatest_(IEnumerable> sources, Func, TResult> resultSelector) + { + return new CombineLatest(sources, resultSelector); + } + + #endregion + + #region + Concat + + + public virtual IObservable Concat(IObservable first, IObservable second) + { + return Concat_(new[] { first, second }); + } + + public virtual IObservable Concat(params IObservable[] sources) + { + return Concat_(sources); + } + + public virtual IObservable Concat(IEnumerable> sources) + { + return Concat_(sources); + } + + private static IObservable Concat_(IEnumerable> sources) + { + return new Concat(sources); + } + + public virtual IObservable Concat(IObservable> sources) + { + return Concat_(sources); + } + + public virtual IObservable Concat(IObservable> sources) + { + return Concat_(Select(sources, TaskObservableExtensions.ToObservable)); + } + + private static IObservable Concat_(IObservable> sources) + { + return new ConcatMany(sources); + } + + #endregion + + #region + Merge + + + public virtual IObservable Merge(IObservable> sources) + { + return Merge_(sources); + } + + public virtual IObservable Merge(IObservable> sources) + { + return new Merge.Tasks(sources); + } + + public virtual IObservable Merge(IObservable> sources, int maxConcurrent) + { + return Merge_(sources, maxConcurrent); + } + + public virtual IObservable Merge(IEnumerable> sources, int maxConcurrent) + { + return Merge_(sources.ToObservable(SchedulerDefaults.ConstantTimeOperations), maxConcurrent); + } + + public virtual IObservable Merge(IEnumerable> sources, int maxConcurrent, IScheduler scheduler) + { + return Merge_(sources.ToObservable(scheduler), maxConcurrent); + } + + public virtual IObservable Merge(IObservable first, IObservable second) + { + return Merge_(new[] { first, second }.ToObservable(SchedulerDefaults.ConstantTimeOperations)); + } + + public virtual IObservable Merge(IObservable first, IObservable second, IScheduler scheduler) + { + return Merge_(new[] { first, second }.ToObservable(scheduler)); + } + + public virtual IObservable Merge(params IObservable[] sources) + { + return Merge_(sources.ToObservable(SchedulerDefaults.ConstantTimeOperations)); + } + + public virtual IObservable Merge(IScheduler scheduler, params IObservable[] sources) + { + return Merge_(sources.ToObservable(scheduler)); + } + + public virtual IObservable Merge(IEnumerable> sources) + { + return Merge_(sources.ToObservable(SchedulerDefaults.ConstantTimeOperations)); + } + + public virtual IObservable Merge(IEnumerable> sources, IScheduler scheduler) + { + return Merge_(sources.ToObservable(scheduler)); + } + + private static IObservable Merge_(IObservable> sources) + { + return new Merge.Observables(sources); + } + + private static IObservable Merge_(IObservable> sources, int maxConcurrent) + { + return new Merge.ObservablesMaxConcurrency(sources, maxConcurrent); + } + + #endregion + + #region + OnErrorResumeNext + + + public virtual IObservable OnErrorResumeNext(IObservable first, IObservable second) + { + return OnErrorResumeNext_(new[] { first, second }); + } + + public virtual IObservable OnErrorResumeNext(params IObservable[] sources) + { + return OnErrorResumeNext_(sources); + } + + public virtual IObservable OnErrorResumeNext(IEnumerable> sources) + { + return OnErrorResumeNext_(sources); + } + + private static IObservable OnErrorResumeNext_(IEnumerable> sources) + { + return new OnErrorResumeNext(sources); + } + + #endregion + + #region + SkipUntil + + + public virtual IObservable SkipUntil(IObservable source, IObservable other) + { + return new SkipUntil(source, other); + } + + #endregion + + #region + Switch + + + public virtual IObservable Switch(IObservable> sources) + { + return Switch_(sources); + } + + public virtual IObservable Switch(IObservable> sources) + { + return Switch_(Select(sources, TaskObservableExtensions.ToObservable)); + } + + private static IObservable Switch_(IObservable> sources) + { + return new Switch(sources); + } + + #endregion + + #region + TakeUntil + + + public virtual IObservable TakeUntil(IObservable source, IObservable other) + { + return new TakeUntil(source, other); + } + + public virtual IObservable TakeUntil(IObservable source, Func stopPredicate) + { + return new TakeUntilPredicate(source, stopPredicate); + } + + #endregion + + #region + Window + + + public virtual IObservable> Window(IObservable source, Func> windowClosingSelector) + { + return new Window.Selector(source, windowClosingSelector); + } + + public virtual IObservable> Window(IObservable source, IObservable windowOpenings, Func> windowClosingSelector) + { + return windowOpenings.GroupJoin(source, windowClosingSelector, _ => Observable.Empty(), (_, window) => window); + } + + public virtual IObservable> Window(IObservable source, IObservable windowBoundaries) + { + return new Window.Boundaries(source, windowBoundaries); + } + + #endregion + + #region + WithLatestFrom + + + public virtual IObservable WithLatestFrom(IObservable first, IObservable second, Func resultSelector) + { + return new WithLatestFrom(first, second, resultSelector); + } + + #endregion + + #region + Zip + + + public virtual IObservable Zip(IObservable first, IObservable second, Func resultSelector) + { + return new Zip.Observable(first, second, resultSelector); + } + + public virtual IObservable Zip(IEnumerable> sources, Func, TResult> resultSelector) + { + return Zip_(sources).Select(resultSelector); + } + + public virtual IObservable> Zip(IEnumerable> sources) + { + return Zip_(sources); + } + + public virtual IObservable> Zip(params IObservable[] sources) + { + return Zip_(sources); + } + + private static IObservable> Zip_(IEnumerable> sources) + { + return new Zip(sources); + } + + public virtual IObservable Zip(IObservable first, IEnumerable second, Func resultSelector) + { + return new Zip.Enumerable(first, second, resultSelector); + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/QueryLanguage.Single.cs b/LibExternal/System.Reactive/Linq/QueryLanguage.Single.cs new file mode 100644 index 0000000..8148971 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QueryLanguage.Single.cs @@ -0,0 +1,378 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Concurrency; + +namespace System.Reactive.Linq +{ + using ObservableImpl; + + internal partial class QueryLanguage + { + #region - Append - + + public virtual IObservable Append(IObservable source, TSource value) + { + return Append_(source, value, SchedulerDefaults.ConstantTimeOperations); + } + + public virtual IObservable Append(IObservable source, TSource value, IScheduler scheduler) + { + return Append_(source, value, scheduler); + } + + private static IObservable Append_(IObservable source, TSource value, IScheduler scheduler) + { + if (source is AppendPrepend.IAppendPrepend ap && ap.Scheduler == scheduler) + { + return ap.Append(value); + } + if (scheduler == ImmediateScheduler.Instance) + { + return new AppendPrepend.SingleImmediate(source, value, append: true); + } + return new AppendPrepend.SingleValue(source, value, scheduler, append: true); + } + + #endregion + + #region + AsObservable + + + public virtual IObservable AsObservable(IObservable source) + { + if (source is AsObservable asObservable) + { + return asObservable; + } + + return new AsObservable(source); + } + + #endregion + + #region + Buffer + + + public virtual IObservable> Buffer(IObservable source, int count) + { + return new Buffer.CountExact(source, count); + } + + public virtual IObservable> Buffer(IObservable source, int count, int skip) + { + if (count > skip) + { + return new Buffer.CountOverlap(source, count, skip); + } + + if (count < skip) + { + return new Buffer.CountSkip(source, count, skip); + } + // count == skip + return new Buffer.CountExact(source, count); + } + + #endregion + + #region + Dematerialize + + + public virtual IObservable Dematerialize(IObservable> source) + { + if (source is Materialize materialize) + { + return materialize.Dematerialize(); + } + + return new Dematerialize(source); + } + + #endregion + + #region + DistinctUntilChanged + + + public virtual IObservable DistinctUntilChanged(IObservable source) + { + return DistinctUntilChanged_(source, x => x, EqualityComparer.Default); + } + + public virtual IObservable DistinctUntilChanged(IObservable source, IEqualityComparer comparer) + { + return DistinctUntilChanged_(source, x => x, comparer); + } + + public virtual IObservable DistinctUntilChanged(IObservable source, Func keySelector) + { + return DistinctUntilChanged_(source, keySelector, EqualityComparer.Default); + } + + public virtual IObservable DistinctUntilChanged(IObservable source, Func keySelector, IEqualityComparer comparer) + { + return DistinctUntilChanged_(source, keySelector, comparer); + } + + private static IObservable DistinctUntilChanged_(IObservable source, Func keySelector, IEqualityComparer comparer) + { + return new DistinctUntilChanged(source, keySelector, comparer); + } + + #endregion + + #region + Do + + + public virtual IObservable Do(IObservable source, Action onNext) + { + return new Do.OnNext(source, onNext); + } + + public virtual IObservable Do(IObservable source, Action onNext, Action onCompleted) + { + return Do_(source, onNext, Stubs.Ignore, onCompleted); + } + + public virtual IObservable Do(IObservable source, Action onNext, Action onError) + { + return Do_(source, onNext, onError, Stubs.Nop); + } + + public virtual IObservable Do(IObservable source, Action onNext, Action onError, Action onCompleted) + { + return Do_(source, onNext, onError, onCompleted); + } + + public virtual IObservable Do(IObservable source, IObserver observer) + { + return new Do.Observer(source, observer); + } + + private static IObservable Do_(IObservable source, Action onNext, Action onError, Action onCompleted) + { + return new Do.Actions(source, onNext, onError, onCompleted); + } + + #endregion + + #region + Finally + + + public virtual IObservable Finally(IObservable source, Action finallyAction) + { + return new Finally(source, finallyAction); + } + + #endregion + + #region + IgnoreElements + + + public virtual IObservable IgnoreElements(IObservable source) + { + if (source is IgnoreElements ignoreElements) + { + return ignoreElements; + } + + return new IgnoreElements(source); + } + + #endregion + + #region + Materialize + + + public virtual IObservable> Materialize(IObservable source) + { + // + // NOTE: Peephole optimization of xs.Dematerialize().Materialize() should not be performed. It's possible for xs to + // contain multiple terminal notifications, which won't survive a Dematerialize().Materialize() chain. In case + // a reduction to xs.AsObservable() would be performed, those notification elements would survive. + // + + return new Materialize(source); + } + + #endregion + + #region - Prepend - + + public virtual IObservable Prepend(IObservable source, TSource value) + { + return Prepend_(source, value, SchedulerDefaults.ConstantTimeOperations); + } + + public virtual IObservable Prepend(IObservable source, TSource value, IScheduler scheduler) + { + return Prepend_(source, value, scheduler); + } + + private static IObservable Prepend_(IObservable source, TSource value, IScheduler scheduler) + { + if (source is AppendPrepend.IAppendPrepend ap && ap.Scheduler == scheduler) + { + return ap.Prepend(value); + } + + if (scheduler == ImmediateScheduler.Instance) + { + return new AppendPrepend.SingleImmediate(source, value, append: false); + } + + return new AppendPrepend.SingleValue(source, value, scheduler, append: false); + } + + #endregion + + #region - Repeat - + + public virtual IObservable Repeat(IObservable source) + { + return RepeatInfinite(source).Concat(); + } + + private static IEnumerable RepeatInfinite(T value) + { + while (true) + { + yield return value; + } + } + + public virtual IObservable Repeat(IObservable source, int repeatCount) + { + return Enumerable.Repeat(source, repeatCount).Concat(); + } + + public virtual IObservable RepeatWhen(IObservable source, Func, IObservable> handler) + { + return new RepeatWhen(source, handler); + } + + #endregion + + #region - Retry - + + public virtual IObservable Retry(IObservable source) + { + return RepeatInfinite(source).Catch(); + } + + public virtual IObservable Retry(IObservable source, int retryCount) + { + return Enumerable.Repeat(source, retryCount).Catch(); + } + + public virtual IObservable RetryWhen(IObservable source, Func, IObservable> handler) + { + return new RetryWhen(source, handler); + } + + + #endregion + + #region + Scan + + + public virtual IObservable Scan(IObservable source, TAccumulate seed, Func accumulator) + { + return new Scan(source, seed, accumulator); + } + + public virtual IObservable Scan(IObservable source, Func accumulator) + { + return new Scan(source, accumulator); + } + + #endregion + + #region + SkipLast + + + public virtual IObservable SkipLast(IObservable source, int count) + { + return new SkipLast.Count(source, count); + } + + #endregion + + #region - StartWith - + + public virtual IObservable StartWith(IObservable source, params TSource[] values) + { + return StartWith_(source, SchedulerDefaults.ConstantTimeOperations, values); + } + + public virtual IObservable StartWith(IObservable source, IScheduler scheduler, params TSource[] values) + { + return StartWith_(source, scheduler, values); + } + + public virtual IObservable StartWith(IObservable source, IEnumerable values) + { + return StartWith(source, SchedulerDefaults.ConstantTimeOperations, values); + } + + public virtual IObservable StartWith(IObservable source, IScheduler scheduler, IEnumerable values) + { + // + // NOTE: For some reason, someone introduced this signature in the Observable class, which is inconsistent with the Rx pattern + // of putting the IScheduler last. It also wasn't wired up through IQueryLanguage. When introducing this method in the + // IQueryLanguage interface, we went for consistency with the public API, hence the odd position of the IScheduler. + // + + if (values is not TSource[] valueArray) + { + var valueList = new List(values); + valueArray = valueList.ToArray(); + } + + return StartWith_(source, scheduler, valueArray); + } + + private static IObservable StartWith_(IObservable source, IScheduler scheduler, params TSource[] values) + { + return values.ToObservable(scheduler).Concat(source); + } + + #endregion + + #region + TakeLast + + + public virtual IObservable TakeLast(IObservable source, int count) + { + return TakeLast_(source, count, SchedulerDefaults.Iteration); + } + + public virtual IObservable TakeLast(IObservable source, int count, IScheduler scheduler) + { + return TakeLast_(source, count, scheduler); + } + + private static IObservable TakeLast_(IObservable source, int count, IScheduler scheduler) + { + return new TakeLast.Count(source, count, scheduler); + } + + public virtual IObservable> TakeLastBuffer(IObservable source, int count) + { + return new TakeLastBuffer.Count(source, count); + } + + #endregion + + #region + Window + + + public virtual IObservable> Window(IObservable source, int count, int skip) + { + return Window_(source, count, skip); + } + + public virtual IObservable> Window(IObservable source, int count) + { + return Window_(source, count, count); + } + + private static IObservable> Window_(IObservable source, int count, int skip) + { + return new Window.Count(source, count, skip); + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/QueryLanguage.StandardSequenceOperators.cs b/LibExternal/System.Reactive/Linq/QueryLanguage.StandardSequenceOperators.cs new file mode 100644 index 0000000..1cb3752 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QueryLanguage.StandardSequenceOperators.cs @@ -0,0 +1,438 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Concurrency; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Reactive.Linq +{ + using ObservableImpl; + + internal partial class QueryLanguage + { + #region + Cast + + + public virtual IObservable Cast(IObservable source) + { + return new Cast(source); + } + + #endregion + + #region + DefaultIfEmpty + + + public virtual IObservable DefaultIfEmpty(IObservable source) + { + return new DefaultIfEmpty(source, default!); + } + + public virtual IObservable DefaultIfEmpty(IObservable source, TSource defaultValue) + { + return new DefaultIfEmpty(source, defaultValue); + } + + #endregion + + #region + Distinct + + + public virtual IObservable Distinct(IObservable source) + { + return new Distinct(source, x => x, EqualityComparer.Default); + } + + public virtual IObservable Distinct(IObservable source, IEqualityComparer comparer) + { + return new Distinct(source, x => x, comparer); + } + + public virtual IObservable Distinct(IObservable source, Func keySelector) + { + return new Distinct(source, keySelector, EqualityComparer.Default); + } + + public virtual IObservable Distinct(IObservable source, Func keySelector, IEqualityComparer comparer) + { + return new Distinct(source, keySelector, comparer); + } + + #endregion + + #region + GroupBy + + + public virtual IObservable> GroupBy(IObservable source, Func keySelector, Func elementSelector) + { + return GroupBy_(source, keySelector, elementSelector, null, EqualityComparer.Default); + } + + public virtual IObservable> GroupBy(IObservable source, Func keySelector, IEqualityComparer comparer) + { + return GroupBy_(source, keySelector, x => x, null, comparer); + } + + public virtual IObservable> GroupBy(IObservable source, Func keySelector) + { + return GroupBy_(source, keySelector, x => x, null, EqualityComparer.Default); + } + + public virtual IObservable> GroupBy(IObservable source, Func keySelector, Func elementSelector, IEqualityComparer comparer) + { + return GroupBy_(source, keySelector, elementSelector, null, comparer); + } + + public virtual IObservable> GroupBy(IObservable source, Func keySelector, Func elementSelector, int capacity) + { + return GroupBy_(source, keySelector, elementSelector, capacity, EqualityComparer.Default); + } + + public virtual IObservable> GroupBy(IObservable source, Func keySelector, int capacity, IEqualityComparer comparer) + { + return GroupBy_(source, keySelector, x => x, capacity, comparer); + } + + public virtual IObservable> GroupBy(IObservable source, Func keySelector, int capacity) + { + return GroupBy_(source, keySelector, x => x, capacity, EqualityComparer.Default); + } + + public virtual IObservable> GroupBy(IObservable source, Func keySelector, Func elementSelector, int capacity, IEqualityComparer comparer) + { + return GroupBy_(source, keySelector, elementSelector, capacity, comparer); + } + + private static IObservable> GroupBy_(IObservable source, Func keySelector, Func elementSelector, int? capacity, IEqualityComparer comparer) + { + return new GroupBy(source, keySelector, elementSelector, capacity, comparer); + } + + #endregion + + #region + GroupByUntil + + + public virtual IObservable> GroupByUntil(IObservable source, Func keySelector, Func elementSelector, Func, IObservable> durationSelector, IEqualityComparer comparer) + { + return GroupByUntil_(source, keySelector, elementSelector, durationSelector, null, comparer); + } + + public virtual IObservable> GroupByUntil(IObservable source, Func keySelector, Func elementSelector, Func, IObservable> durationSelector) + { + return GroupByUntil_(source, keySelector, elementSelector, durationSelector, null, EqualityComparer.Default); + } + + public virtual IObservable> GroupByUntil(IObservable source, Func keySelector, Func, IObservable> durationSelector, IEqualityComparer comparer) + { + return GroupByUntil_(source, keySelector, x => x, durationSelector, null, comparer); + } + + public virtual IObservable> GroupByUntil(IObservable source, Func keySelector, Func, IObservable> durationSelector) + { + return GroupByUntil_(source, keySelector, x => x, durationSelector, null, EqualityComparer.Default); + } + + public virtual IObservable> GroupByUntil(IObservable source, Func keySelector, Func elementSelector, Func, IObservable> durationSelector, int capacity, IEqualityComparer comparer) + { + return GroupByUntil_(source, keySelector, elementSelector, durationSelector, capacity, comparer); + } + + public virtual IObservable> GroupByUntil(IObservable source, Func keySelector, Func elementSelector, Func, IObservable> durationSelector, int capacity) + { + return GroupByUntil_(source, keySelector, elementSelector, durationSelector, capacity, EqualityComparer.Default); + } + + public virtual IObservable> GroupByUntil(IObservable source, Func keySelector, Func, IObservable> durationSelector, int capacity, IEqualityComparer comparer) + { + return GroupByUntil_(source, keySelector, x => x, durationSelector, capacity, comparer); + } + + public virtual IObservable> GroupByUntil(IObservable source, Func keySelector, Func, IObservable> durationSelector, int capacity) + { + return GroupByUntil_(source, keySelector, x => x, durationSelector, capacity, EqualityComparer.Default); + } + + private static IObservable> GroupByUntil_(IObservable source, Func keySelector, Func elementSelector, Func, IObservable> durationSelector, int? capacity, IEqualityComparer comparer) + { + return new GroupByUntil(source, keySelector, elementSelector, durationSelector, capacity, comparer); + } + + #endregion + + #region + GroupJoin + + + public virtual IObservable GroupJoin(IObservable left, IObservable right, Func> leftDurationSelector, Func> rightDurationSelector, Func, TResult> resultSelector) + { + return GroupJoin_(left, right, leftDurationSelector, rightDurationSelector, resultSelector); + } + + private static IObservable GroupJoin_(IObservable left, IObservable right, Func> leftDurationSelector, Func> rightDurationSelector, Func, TResult> resultSelector) + { + return new GroupJoin(left, right, leftDurationSelector, rightDurationSelector, resultSelector); + } + + #endregion + + #region + Join + + + public virtual IObservable Join(IObservable left, IObservable right, Func> leftDurationSelector, Func> rightDurationSelector, Func resultSelector) + { + return Join_(left, right, leftDurationSelector, rightDurationSelector, resultSelector); + } + + private static IObservable Join_(IObservable left, IObservable right, Func> leftDurationSelector, Func> rightDurationSelector, Func resultSelector) + { + return new Join(left, right, leftDurationSelector, rightDurationSelector, resultSelector); + } + + #endregion + + #region + OfType + + + public virtual IObservable OfType(IObservable source) + { + return new OfType(source); + } + + #endregion + + #region + Select + + + public virtual IObservable Select(IObservable source, Func selector) + { + // CONSIDER: Add fusion for Select/Select pairs. + + return new Select.Selector(source, selector); + } + + public virtual IObservable Select(IObservable source, Func selector) + { + return new Select.SelectorIndexed(source, selector); + } + + #endregion + + #region + SelectMany + + + public virtual IObservable SelectMany(IObservable source, IObservable other) + { + return SelectMany_(source, _ => other); + } + + public virtual IObservable SelectMany(IObservable source, Func> selector) + { + return SelectMany_(source, selector); + } + + public virtual IObservable SelectMany(IObservable source, Func> selector) + { + return SelectMany_(source, selector); + } + + public virtual IObservable SelectMany(IObservable source, Func> selector) + { + return new SelectMany.TaskSelector(source, (x, token) => selector(x)); + } + + public virtual IObservable SelectMany(IObservable source, Func> selector) + { + return new SelectMany.TaskSelectorIndexed(source, (x, i, token) => selector(x, i)); + } + + public virtual IObservable SelectMany(IObservable source, Func> selector) + { + return new SelectMany.TaskSelector(source, selector); + } + + public virtual IObservable SelectMany(IObservable source, Func> selector) + { + return new SelectMany.TaskSelectorIndexed(source, selector); + } + + public virtual IObservable SelectMany(IObservable source, Func> collectionSelector, Func resultSelector) + { + return SelectMany_(source, collectionSelector, resultSelector); + } + + public virtual IObservable SelectMany(IObservable source, Func> collectionSelector, Func resultSelector) + { + return SelectMany_(source, collectionSelector, resultSelector); + } + + public virtual IObservable SelectMany(IObservable source, Func> taskSelector, Func resultSelector) + { + return new SelectMany.TaskSelector(source, (x, token) => taskSelector(x), resultSelector); + } + + public virtual IObservable SelectMany(IObservable source, Func> taskSelector, Func resultSelector) + { + return new SelectMany.TaskSelectorIndexed(source, (x, i, token) => taskSelector(x, i), resultSelector); + } + + public virtual IObservable SelectMany(IObservable source, Func> taskSelector, Func resultSelector) + { + return new SelectMany.TaskSelector(source, taskSelector, resultSelector); + } + + public virtual IObservable SelectMany(IObservable source, Func> taskSelector, Func resultSelector) + { + return new SelectMany.TaskSelectorIndexed(source, taskSelector, resultSelector); + } + + private static IObservable SelectMany_(IObservable source, Func> selector) + { + return new SelectMany.ObservableSelector(source, selector); + } + + private static IObservable SelectMany_(IObservable source, Func> selector) + { + return new SelectMany.ObservableSelectorIndexed(source, selector); + } + + private static IObservable SelectMany_(IObservable source, Func> collectionSelector, Func resultSelector) + { + return new SelectMany.ObservableSelector(source, collectionSelector, resultSelector); + } + + private static IObservable SelectMany_(IObservable source, Func> collectionSelector, Func resultSelector) + { + return new SelectMany.ObservableSelectorIndexed(source, collectionSelector, resultSelector); + } + + public virtual IObservable SelectMany(IObservable source, Func> onNext, Func> onError, Func> onCompleted) + { + return new SelectMany.ObservableSelectors(source, onNext, onError, onCompleted); + } + + public virtual IObservable SelectMany(IObservable source, Func> onNext, Func> onError, Func> onCompleted) + { + return new SelectMany.ObservableSelectorsIndexed(source, onNext, onError, onCompleted); + } + + public virtual IObservable SelectMany(IObservable source, Func> selector) + { + return new SelectMany.EnumerableSelector(source, selector); + } + + public virtual IObservable SelectMany(IObservable source, Func> selector) + { + return new SelectMany.EnumerableSelectorIndexed(source, selector); + } + + public virtual IObservable SelectMany(IObservable source, Func> collectionSelector, Func resultSelector) + { + return SelectMany_(source, collectionSelector, resultSelector); + } + + public virtual IObservable SelectMany(IObservable source, Func> collectionSelector, Func resultSelector) + { + return SelectMany_(source, collectionSelector, resultSelector); + } + + private static IObservable SelectMany_(IObservable source, Func> collectionSelector, Func resultSelector) + { + return new SelectMany.EnumerableSelector(source, collectionSelector, resultSelector); + } + + private static IObservable SelectMany_(IObservable source, Func> collectionSelector, Func resultSelector) + { + return new SelectMany.EnumerableSelectorIndexed(source, collectionSelector, resultSelector); + } + + #endregion + + #region + Skip + + + public virtual IObservable Skip(IObservable source, int count) + { + if (source is Skip.Count skip) + { + return skip.Combine(count); + } + + return new Skip.Count(source, count); + } + + #endregion + + #region + SkipWhile + + + public virtual IObservable SkipWhile(IObservable source, Func predicate) + { + return new SkipWhile.Predicate(source, predicate); + } + + public virtual IObservable SkipWhile(IObservable source, Func predicate) + { + return new SkipWhile.PredicateIndexed(source, predicate); + } + + #endregion + + #region + Take + + + public virtual IObservable Take(IObservable source, int count) + { + if (count == 0) + { + return Empty(); + } + + return Take_(source, count); + } + + public virtual IObservable Take(IObservable source, int count, IScheduler scheduler) + { + if (count == 0) + { + return Empty(scheduler); + } + + return Take_(source, count); + } + + private static IObservable Take_(IObservable source, int count) + { + if (source is Take.Count take) + { + return take.Combine(count); + } + + return new Take.Count(source, count); + } + + #endregion + + #region + TakeWhile + + + public virtual IObservable TakeWhile(IObservable source, Func predicate) + { + return new TakeWhile.Predicate(source, predicate); + } + + public virtual IObservable TakeWhile(IObservable source, Func predicate) + { + return new TakeWhile.PredicateIndexed(source, predicate); + } + + #endregion + + #region + Where + + + public virtual IObservable Where(IObservable source, Func predicate) + { + if (source is Where.Predicate where) + { + return where.Combine(predicate); + } + + return new Where.Predicate(source, predicate); + } + + public virtual IObservable Where(IObservable source, Func predicate) + { + return new Where.PredicateIndexed(source, predicate); + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/QueryLanguage.Time.cs b/LibExternal/System.Reactive/Linq/QueryLanguage.Time.cs new file mode 100644 index 0000000..7ae2cb2 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QueryLanguage.Time.cs @@ -0,0 +1,666 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Concurrency; + +namespace System.Reactive.Linq +{ + using ObservableImpl; + + internal partial class QueryLanguage + { + #region + Buffer + + + #region TimeSpan only + + public virtual IObservable> Buffer(IObservable source, TimeSpan timeSpan) + { + return Buffer_(source, timeSpan, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable> Buffer(IObservable source, TimeSpan timeSpan, IScheduler scheduler) + { + return Buffer_(source, timeSpan, scheduler); + } + + private static IObservable> Buffer_(IObservable source, TimeSpan timeSpan, IScheduler scheduler) + { + return new Buffer.TimeHopping(source, timeSpan, scheduler); + } + + public virtual IObservable> Buffer(IObservable source, TimeSpan timeSpan, TimeSpan timeShift) + { + return Buffer_(source, timeSpan, timeShift, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable> Buffer(IObservable source, TimeSpan timeSpan, TimeSpan timeShift, IScheduler scheduler) + { + return Buffer_(source, timeSpan, timeShift, scheduler); + } + + private static IObservable> Buffer_(IObservable source, TimeSpan timeSpan, TimeSpan timeShift, IScheduler scheduler) + { + return new Buffer.TimeSliding(source, timeSpan, timeShift, scheduler); + } + + #endregion + + #region TimeSpan + int + + public virtual IObservable> Buffer(IObservable source, TimeSpan timeSpan, int count) + { + return Buffer_(source, timeSpan, count, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable> Buffer(IObservable source, TimeSpan timeSpan, int count, IScheduler scheduler) + { + return Buffer_(source, timeSpan, count, scheduler); + } + + private static IObservable> Buffer_(IObservable source, TimeSpan timeSpan, int count, IScheduler scheduler) + { + return new Buffer.Ferry(source, timeSpan, count, scheduler); + } + + #endregion + + #endregion + + #region + Delay + + + #region TimeSpan + + public virtual IObservable Delay(IObservable source, TimeSpan dueTime) + { + return Delay_(source, dueTime, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable Delay(IObservable source, TimeSpan dueTime, IScheduler scheduler) + { + return Delay_(source, dueTime, scheduler); + } + + private static IObservable Delay_(IObservable source, TimeSpan dueTime, IScheduler scheduler) + { + return new Delay.Relative(source, dueTime, scheduler); + } + + #endregion + + #region DateTimeOffset + + public virtual IObservable Delay(IObservable source, DateTimeOffset dueTime) + { + return Delay_(source, dueTime, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable Delay(IObservable source, DateTimeOffset dueTime, IScheduler scheduler) + { + return Delay_(source, dueTime, scheduler); + } + + private static IObservable Delay_(IObservable source, DateTimeOffset dueTime, IScheduler scheduler) + { + return new Delay.Absolute(source, dueTime, scheduler); + } + + #endregion + + #region Duration selector + + public virtual IObservable Delay(IObservable source, Func> delayDurationSelector) + { + return new Delay.Selector(source, delayDurationSelector); + } + + public virtual IObservable Delay(IObservable source, IObservable subscriptionDelay, Func> delayDurationSelector) + { + return new Delay.SelectorWithSubscriptionDelay(source, subscriptionDelay, delayDurationSelector); + } + + #endregion + + #endregion + + #region + DelaySubscription + + + public virtual IObservable DelaySubscription(IObservable source, TimeSpan dueTime) + { + return DelaySubscription_(source, dueTime, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable DelaySubscription(IObservable source, TimeSpan dueTime, IScheduler scheduler) + { + return DelaySubscription_(source, dueTime, scheduler); + } + + private static IObservable DelaySubscription_(IObservable source, TimeSpan dueTime, IScheduler scheduler) + { + return new DelaySubscription.Relative(source, dueTime, scheduler); + } + + public virtual IObservable DelaySubscription(IObservable source, DateTimeOffset dueTime) + { + return DelaySubscription_(source, dueTime, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable DelaySubscription(IObservable source, DateTimeOffset dueTime, IScheduler scheduler) + { + return DelaySubscription_(source, dueTime, scheduler); + } + + private static IObservable DelaySubscription_(IObservable source, DateTimeOffset dueTime, IScheduler scheduler) + { + return new DelaySubscription.Absolute(source, dueTime, scheduler); + } + + #endregion + + #region + Generate + + + public virtual IObservable Generate(TState initialState, Func condition, Func iterate, Func resultSelector, Func timeSelector) + { + return Generate_(initialState, condition, iterate, resultSelector, timeSelector, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable Generate(TState initialState, Func condition, Func iterate, Func resultSelector, Func timeSelector, IScheduler scheduler) + { + return Generate_(initialState, condition, iterate, resultSelector, timeSelector, scheduler); + } + + private static IObservable Generate_(TState initialState, Func condition, Func iterate, Func resultSelector, Func timeSelector, IScheduler scheduler) + { + return new Generate.Relative(initialState, condition, iterate, resultSelector, timeSelector, scheduler); + } + + public virtual IObservable Generate(TState initialState, Func condition, Func iterate, Func resultSelector, Func timeSelector) + { + return Generate_(initialState, condition, iterate, resultSelector, timeSelector, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable Generate(TState initialState, Func condition, Func iterate, Func resultSelector, Func timeSelector, IScheduler scheduler) + { + return Generate_(initialState, condition, iterate, resultSelector, timeSelector, scheduler); + } + + private static IObservable Generate_(TState initialState, Func condition, Func iterate, Func resultSelector, Func timeSelector, IScheduler scheduler) + { + return new Generate.Absolute(initialState, condition, iterate, resultSelector, timeSelector, scheduler); + } + + #endregion + + #region + Interval + + + public virtual IObservable Interval(TimeSpan period) + { + return Timer_(period, period, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable Interval(TimeSpan period, IScheduler scheduler) + { + return Timer_(period, period, scheduler); + } + + #endregion + + #region + Sample + + + public virtual IObservable Sample(IObservable source, TimeSpan interval) + { + return Sample_(source, interval, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable Sample(IObservable source, TimeSpan interval, IScheduler scheduler) + { + return Sample_(source, interval, scheduler); + } + + private static IObservable Sample_(IObservable source, TimeSpan interval, IScheduler scheduler) + { + return new Sample(source, interval, scheduler); + } + + public virtual IObservable Sample(IObservable source, IObservable sampler) + { + return Sample_(source, sampler); + } + + private static IObservable Sample_(IObservable source, IObservable sampler) + { + return new Sample(source, sampler); + } + + #endregion + + #region + Skip + + + public virtual IObservable Skip(IObservable source, TimeSpan duration) + { + return Skip_(source, duration, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable Skip(IObservable source, TimeSpan duration, IScheduler scheduler) + { + return Skip_(source, duration, scheduler); + } + + private static IObservable Skip_(IObservable source, TimeSpan duration, IScheduler scheduler) + { + if (source is Skip.Time skip && skip._scheduler == scheduler) + { + return skip.Combine(duration); + } + + return new Skip.Time(source, duration, scheduler); + } + + #endregion + + #region + SkipLast + + + public virtual IObservable SkipLast(IObservable source, TimeSpan duration) + { + return SkipLast_(source, duration, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable SkipLast(IObservable source, TimeSpan duration, IScheduler scheduler) + { + return SkipLast_(source, duration, scheduler); + } + + private static IObservable SkipLast_(IObservable source, TimeSpan duration, IScheduler scheduler) + { + return new SkipLast.Time(source, duration, scheduler); + } + + #endregion + + #region + SkipUntil + + + public virtual IObservable SkipUntil(IObservable source, DateTimeOffset startTime) + { + return SkipUntil_(source, startTime, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable SkipUntil(IObservable source, DateTimeOffset startTime, IScheduler scheduler) + { + return SkipUntil_(source, startTime, scheduler); + } + + private static IObservable SkipUntil_(IObservable source, DateTimeOffset startTime, IScheduler scheduler) + { + if (source is SkipUntil skipUntil && skipUntil._scheduler == scheduler) + { + return skipUntil.Combine(startTime); + } + + return new SkipUntil(source, startTime, scheduler); + } + + #endregion + + #region + Take + + + public virtual IObservable Take(IObservable source, TimeSpan duration) + { + return Take_(source, duration, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable Take(IObservable source, TimeSpan duration, IScheduler scheduler) + { + return Take_(source, duration, scheduler); + } + + private static IObservable Take_(IObservable source, TimeSpan duration, IScheduler scheduler) + { + if (source is Take.Time take && take._scheduler == scheduler) + { + return take.Combine(duration); + } + + return new Take.Time(source, duration, scheduler); + } + + #endregion + + #region + TakeLast + + + public virtual IObservable TakeLast(IObservable source, TimeSpan duration) + { + return TakeLast_(source, duration, SchedulerDefaults.TimeBasedOperations, SchedulerDefaults.Iteration); + } + + public virtual IObservable TakeLast(IObservable source, TimeSpan duration, IScheduler scheduler) + { + return TakeLast_(source, duration, scheduler, SchedulerDefaults.Iteration); + } + + public virtual IObservable TakeLast(IObservable source, TimeSpan duration, IScheduler timerScheduler, IScheduler loopScheduler) + { + return TakeLast_(source, duration, timerScheduler, loopScheduler); + } + + private static IObservable TakeLast_(IObservable source, TimeSpan duration, IScheduler timerScheduler, IScheduler loopScheduler) + { + return new TakeLast.Time(source, duration, timerScheduler, loopScheduler); + } + + public virtual IObservable> TakeLastBuffer(IObservable source, TimeSpan duration) + { + return TakeLastBuffer_(source, duration, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable> TakeLastBuffer(IObservable source, TimeSpan duration, IScheduler scheduler) + { + return TakeLastBuffer_(source, duration, scheduler); + } + + private static IObservable> TakeLastBuffer_(IObservable source, TimeSpan duration, IScheduler scheduler) + { + return new TakeLastBuffer.Time(source, duration, scheduler); + } + + #endregion + + #region + TakeUntil + + + public virtual IObservable TakeUntil(IObservable source, DateTimeOffset endTime) + { + return TakeUntil_(source, endTime, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable TakeUntil(IObservable source, DateTimeOffset endTime, IScheduler scheduler) + { + return TakeUntil_(source, endTime, scheduler); + } + + private static IObservable TakeUntil_(IObservable source, DateTimeOffset endTime, IScheduler scheduler) + { + if (source is TakeUntil takeUntil && takeUntil._scheduler == scheduler) + { + return takeUntil.Combine(endTime); + } + + return new TakeUntil(source, endTime, scheduler); + } + + #endregion + + #region + Throttle + + + public virtual IObservable Throttle(IObservable source, TimeSpan dueTime) + { + return Throttle_(source, dueTime, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable Throttle(IObservable source, TimeSpan dueTime, IScheduler scheduler) + { + return Throttle_(source, dueTime, scheduler); + } + + private static IObservable Throttle_(IObservable source, TimeSpan dueTime, IScheduler scheduler) + { + return new Throttle(source, dueTime, scheduler); + } + + public virtual IObservable Throttle(IObservable source, Func> throttleDurationSelector) + { + return new Throttle(source, throttleDurationSelector); + } + + #endregion + + #region + TimeInterval + + + public virtual IObservable> TimeInterval(IObservable source) + { + return TimeInterval_(source, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable> TimeInterval(IObservable source, IScheduler scheduler) + { + return TimeInterval_(source, scheduler); + } + + private static IObservable> TimeInterval_(IObservable source, IScheduler scheduler) + { + return new TimeInterval(source, scheduler); + } + + #endregion + + #region + Timeout + + + #region TimeSpan + + public virtual IObservable Timeout(IObservable source, TimeSpan dueTime) + { + return Timeout_(source, dueTime, Observable.Throw(new TimeoutException()), SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable Timeout(IObservable source, TimeSpan dueTime, IScheduler scheduler) + { + return Timeout_(source, dueTime, Observable.Throw(new TimeoutException()), scheduler); + } + + public virtual IObservable Timeout(IObservable source, TimeSpan dueTime, IObservable other) + { + return Timeout_(source, dueTime, other, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable Timeout(IObservable source, TimeSpan dueTime, IObservable other, IScheduler scheduler) + { + return Timeout_(source, dueTime, other, scheduler); + } + + private static IObservable Timeout_(IObservable source, TimeSpan dueTime, IObservable other, IScheduler scheduler) + { + return new Timeout.Relative(source, dueTime, other, scheduler); + } + + #endregion + + #region DateTimeOffset + + public virtual IObservable Timeout(IObservable source, DateTimeOffset dueTime) + { + return Timeout_(source, dueTime, Observable.Throw(new TimeoutException()), SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable Timeout(IObservable source, DateTimeOffset dueTime, IScheduler scheduler) + { + return Timeout_(source, dueTime, Observable.Throw(new TimeoutException()), scheduler); + } + + public virtual IObservable Timeout(IObservable source, DateTimeOffset dueTime, IObservable other) + { + return Timeout_(source, dueTime, other, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable Timeout(IObservable source, DateTimeOffset dueTime, IObservable other, IScheduler scheduler) + { + return Timeout_(source, dueTime, other, scheduler); + } + + private static IObservable Timeout_(IObservable source, DateTimeOffset dueTime, IObservable other, IScheduler scheduler) + { + return new Timeout.Absolute(source, dueTime, other, scheduler); + } + + #endregion + + #region Duration selector + + public virtual IObservable Timeout(IObservable source, Func> timeoutDurationSelector) + { + return Timeout_(source, Observable.Never(), timeoutDurationSelector, Observable.Throw(new TimeoutException())); + } + + public virtual IObservable Timeout(IObservable source, Func> timeoutDurationSelector, IObservable other) + { + return Timeout_(source, Observable.Never(), timeoutDurationSelector, other); + } + + public virtual IObservable Timeout(IObservable source, IObservable firstTimeout, Func> timeoutDurationSelector) + { + return Timeout_(source, firstTimeout, timeoutDurationSelector, Observable.Throw(new TimeoutException())); + } + + public virtual IObservable Timeout(IObservable source, IObservable firstTimeout, Func> timeoutDurationSelector, IObservable other) + { + return Timeout_(source, firstTimeout, timeoutDurationSelector, other); + } + + private static IObservable Timeout_(IObservable source, IObservable firstTimeout, Func> timeoutDurationSelector, IObservable other) + { + return new Timeout(source, firstTimeout, timeoutDurationSelector, other); + } + + #endregion + + #endregion + + #region + Timer + + + public virtual IObservable Timer(TimeSpan dueTime) + { + return Timer_(dueTime, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable Timer(DateTimeOffset dueTime) + { + return Timer_(dueTime, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable Timer(TimeSpan dueTime, TimeSpan period) + { + return Timer_(dueTime, period, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable Timer(DateTimeOffset dueTime, TimeSpan period) + { + return Timer_(dueTime, period, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable Timer(TimeSpan dueTime, IScheduler scheduler) + { + return Timer_(dueTime, scheduler); + } + + public virtual IObservable Timer(DateTimeOffset dueTime, IScheduler scheduler) + { + return Timer_(dueTime, scheduler); + } + + public virtual IObservable Timer(TimeSpan dueTime, TimeSpan period, IScheduler scheduler) + { + return Timer_(dueTime, period, scheduler); + } + + public virtual IObservable Timer(DateTimeOffset dueTime, TimeSpan period, IScheduler scheduler) + { + return Timer_(dueTime, period, scheduler); + } + + private static IObservable Timer_(TimeSpan dueTime, IScheduler scheduler) + { + return new Timer.Single.Relative(dueTime, scheduler); + } + + private static IObservable Timer_(TimeSpan dueTime, TimeSpan period, IScheduler scheduler) + { + return new Timer.Periodic.Relative(dueTime, period, scheduler); + } + + private static IObservable Timer_(DateTimeOffset dueTime, IScheduler scheduler) + { + return new Timer.Single.Absolute(dueTime, scheduler); + } + + private static IObservable Timer_(DateTimeOffset dueTime, TimeSpan period, IScheduler scheduler) + { + return new Timer.Periodic.Absolute(dueTime, period, scheduler); + } + + #endregion + + #region + Timestamp + + + public virtual IObservable> Timestamp(IObservable source) + { + return Timestamp_(source, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable> Timestamp(IObservable source, IScheduler scheduler) + { + return Timestamp_(source, scheduler); + } + + private static IObservable> Timestamp_(IObservable source, IScheduler scheduler) + { + return new Timestamp(source, scheduler); + } + + #endregion + + #region + Window + + + #region TimeSpan only + + public virtual IObservable> Window(IObservable source, TimeSpan timeSpan) + { + return Window_(source, timeSpan, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable> Window(IObservable source, TimeSpan timeSpan, IScheduler scheduler) + { + return Window_(source, timeSpan, scheduler); + } + + private static IObservable> Window_(IObservable source, TimeSpan timeSpan, IScheduler scheduler) + { + return new Window.TimeHopping(source, timeSpan, scheduler); + } + + public virtual IObservable> Window(IObservable source, TimeSpan timeSpan, TimeSpan timeShift) + { + return Window_(source, timeSpan, timeShift, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable> Window(IObservable source, TimeSpan timeSpan, TimeSpan timeShift, IScheduler scheduler) + { + return Window_(source, timeSpan, timeShift, scheduler); + } + + private static IObservable> Window_(IObservable source, TimeSpan timeSpan, TimeSpan timeShift, IScheduler scheduler) + { + return new Window.TimeSliding(source, timeSpan, timeShift, scheduler); + } + + #endregion + + #region TimeSpan + int + + public virtual IObservable> Window(IObservable source, TimeSpan timeSpan, int count) + { + return Window_(source, timeSpan, count, SchedulerDefaults.TimeBasedOperations); + } + + public virtual IObservable> Window(IObservable source, TimeSpan timeSpan, int count, IScheduler scheduler) + { + return Window_(source, timeSpan, count, scheduler); + } + + private static IObservable> Window_(IObservable source, TimeSpan timeSpan, int count, IScheduler scheduler) + { + return new Window.Ferry(source, timeSpan, count, scheduler); + } + + #endregion + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/QueryLanguageEx.cs b/LibExternal/System.Reactive/Linq/QueryLanguageEx.cs new file mode 100644 index 0000000..2be72f3 --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QueryLanguageEx.cs @@ -0,0 +1,592 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Concurrency; +using System.Reactive.Disposables; +using System.Reactive.Subjects; + +namespace System.Reactive.Linq +{ + using ObservableImpl; + + internal partial class QueryLanguageEx : IQueryLanguageEx + { + #region Create + + public virtual IObservable Create(Func, IEnumerable>> iteratorMethod) + { + return new CreateWithEnumerableObservable(iteratorMethod); + } + + private sealed class CreateWithEnumerableObservable : ObservableBase + { + private readonly Func, IEnumerable>> _iteratorMethod; + + public CreateWithEnumerableObservable(Func, IEnumerable>> iteratorMethod) + { + _iteratorMethod = iteratorMethod; + } + + protected override IDisposable SubscribeCore(IObserver observer) + { + return _iteratorMethod(observer) + .Concat() + .Subscribe(new TerminalOnlyObserver(observer)); + } + } + + private sealed class TerminalOnlyObserver : IObserver + { + private readonly IObserver _observer; + + public TerminalOnlyObserver(IObserver observer) + { + _observer = observer; + } + + public void OnCompleted() + { + _observer.OnCompleted(); + } + + public void OnError(Exception error) + { + _observer.OnError(error); + } + + public void OnNext(object value) + { + // deliberately ignored + } + } + + public virtual IObservable Create(Func>> iteratorMethod) + { + return new CreateWithOnlyEnumerableObservable(iteratorMethod); + } + + private sealed class CreateWithOnlyEnumerableObservable : ObservableBase + { + private readonly Func>> _iteratorMethod; + + public CreateWithOnlyEnumerableObservable(Func>> iteratorMethod) + { + _iteratorMethod = iteratorMethod; + } + + protected override IDisposable SubscribeCore(IObserver observer) + { + return _iteratorMethod() + .Concat() + .Subscribe(new TerminalOnlyObserver(observer)); + } + } + + #endregion + + #region Expand + + public virtual IObservable Expand(IObservable source, Func> selector, IScheduler scheduler) + { + return new ExpandObservable(source, selector, scheduler); + } + + private sealed class ExpandObservable : ObservableBase + { + private readonly IObservable _source; + private readonly Func> _selector; + private readonly IScheduler _scheduler; + + public ExpandObservable(IObservable source, Func> selector, IScheduler scheduler) + { + _source = source; + _selector = selector; + _scheduler = scheduler; + } + + protected override IDisposable SubscribeCore(IObserver observer) + { + var outGate = new object(); + var q = new Queue>(); + var m = new SerialDisposable(); + var d = new CompositeDisposable { m }; + var activeCount = 0; + var isAcquired = false; + + void ensureActive() + { + var isOwner = false; + + lock (q) + { + if (q.Count > 0) + { + isOwner = !isAcquired; + isAcquired = true; + } + } + + if (isOwner) + { + m.Disposable = _scheduler.Schedule(self => + { + IObservable work; + + lock (q) + { + if (q.Count > 0) + { + work = q.Dequeue(); + } + else + { + isAcquired = false; + return; + } + } + + var m1 = new SingleAssignmentDisposable(); + d.Add(m1); + m1.Disposable = work.Subscribe( + x => + { + lock (outGate) + { + observer.OnNext(x); + } + + IObservable result; + try + { + result = _selector(x); + } + catch (Exception exception) + { + lock (outGate) + { + observer.OnError(exception); + } + + return; + } + + lock (q) + { + q.Enqueue(result); + activeCount++; + } + + ensureActive(); + }, + exception => + { + lock (outGate) + { + observer.OnError(exception); + } + }, + () => + { + d.Remove(m1); + + var done = false; + lock (q) + { + activeCount--; + if (activeCount == 0) + { + done = true; + } + } + if (done) + { + lock (outGate) + { + observer.OnCompleted(); + } + } + }); + self(); + }); + } + } + + lock (q) + { + q.Enqueue(_source); + activeCount++; + } + ensureActive(); + + return d; + } + } + + public virtual IObservable Expand(IObservable source, Func> selector) + { + return source.Expand(selector, SchedulerDefaults.Iteration); + } + + #endregion + + #region ForkJoin + + public virtual IObservable ForkJoin(IObservable first, IObservable second, Func resultSelector) + { + return Combine(first, second, (observer, leftSubscription, rightSubscription) => + { + var leftStopped = false; + var rightStopped = false; + var hasLeft = false; + var hasRight = false; + var lastLeft = default(TFirst); + var lastRight = default(TSecond); + + return new BinaryObserver( + left => + { + switch (left.Kind) + { + case NotificationKind.OnNext: + hasLeft = true; + lastLeft = left.Value; + break; + case NotificationKind.OnError: + rightSubscription.Dispose(); + observer.OnError(left.Exception!); + break; + case NotificationKind.OnCompleted: + leftStopped = true; + if (rightStopped) + { + if (!hasLeft) + { + observer.OnCompleted(); + } + else if (!hasRight) + { + observer.OnCompleted(); + } + else + { + TResult result; + try + { + result = resultSelector(lastLeft!, lastRight!); + } + catch (Exception exception) + { + observer.OnError(exception); + return; + } + observer.OnNext(result); + observer.OnCompleted(); + } + } + break; + } + }, + right => + { + switch (right.Kind) + { + case NotificationKind.OnNext: + hasRight = true; + lastRight = right.Value; + break; + case NotificationKind.OnError: + leftSubscription.Dispose(); + observer.OnError(right.Exception!); + break; + case NotificationKind.OnCompleted: + rightStopped = true; + if (leftStopped) + { + if (!hasLeft) + { + observer.OnCompleted(); + } + else if (!hasRight) + { + observer.OnCompleted(); + } + else + { + TResult result; + try + { + result = resultSelector(lastLeft!, lastRight!); + } + catch (Exception exception) + { + observer.OnError(exception); + return; + } + observer.OnNext(result); + observer.OnCompleted(); + } + } + break; + } + }); + }); + } + + public virtual IObservable ForkJoin(params IObservable[] sources) + { + return sources.ForkJoin(); + } + + public virtual IObservable ForkJoin(IEnumerable> sources) + { + return new ForkJoinObservable(sources); + } + + private sealed class ForkJoinObservable : ObservableBase + { + private readonly IEnumerable> _sources; + + public ForkJoinObservable(IEnumerable> sources) + { + _sources = sources; + } + + protected override IDisposable SubscribeCore(IObserver observer) + { + var allSources = _sources.ToArray(); + var count = allSources.Length; + + if (count == 0) + { + observer.OnCompleted(); + return Disposable.Empty; + } + + var group = new CompositeDisposable(allSources.Length); + var gate = new object(); + + var finished = false; + var hasResults = new bool[count]; + var hasCompleted = new bool[count]; + var results = new List(count); + + lock (gate) + { + for (var index = 0; index < count; index++) + { + var currentIndex = index; + var source = allSources[index]; + results.Add(default!); // NB: Reserves a space; the default value gets overwritten below. + group.Add(source.Subscribe( + value => + { + lock (gate) + { + if (!finished) + { + hasResults[currentIndex] = true; + results[currentIndex] = value; + } + } + }, + error => + { + lock (gate) + { + finished = true; + observer.OnError(error); + group.Dispose(); + } + }, + () => + { + lock (gate) + { + if (!finished) + { + if (!hasResults[currentIndex]) + { + observer.OnCompleted(); + return; + } + hasCompleted[currentIndex] = true; + foreach (var completed in hasCompleted) + { + if (!completed) + { + return; + } + } + finished = true; + observer.OnNext(results.ToArray()); + observer.OnCompleted(); + } + } + })); + } + } + return group; + } + } + + #endregion + + #region Let + + public virtual IObservable Let(IObservable source, Func, IObservable> function) + { + return function(source); + } + + #endregion + + #region ManySelect + + public virtual IObservable ManySelect(IObservable source, Func, TResult> selector) + { + return ManySelect(source, selector, DefaultScheduler.Instance); + } + + public virtual IObservable ManySelect(IObservable source, Func, TResult> selector, IScheduler scheduler) + { + return Observable.Defer(() => + { + ChainObservable? chain = null; + + return source + .Select( + x => + { + var curr = new ChainObservable(x); + + chain?.OnNext(curr); + + chain = curr; + + return (IObservable)curr; + }) + .Do( + _ => { }, + exception => + { + chain?.OnError(exception); + }, + () => + { + chain?.OnCompleted(); + }) + .ObserveOn(scheduler) + .Select(selector); + }); + } + + private class ChainObservable : ISubject, T> + { + private readonly T _head; + private readonly AsyncSubject> _tail = new(); + + public ChainObservable(T head) + { + _head = head; + } + + public IDisposable Subscribe(IObserver observer) + { + var g = new CompositeDisposable(); + g.Add(CurrentThreadScheduler.Instance.ScheduleAction((observer, g, @this: this), + state => + { + state.observer.OnNext(state.@this._head); + state.g.Add(state.@this._tail.Merge().Subscribe(state.observer)); + })); + return g; + } + + public void OnCompleted() + { + OnNext(Observable.Empty()); + } + + public void OnError(Exception error) + { + OnNext(Observable.Throw(error)); + } + + public void OnNext(IObservable value) + { + _tail.OnNext(value); + _tail.OnCompleted(); + } + } + + #endregion + + #region ToListObservable + + public virtual ListObservable ToListObservable(IObservable source) + { + return new ListObservable(source); + } + + #endregion + + #region WithLatestFrom + + public virtual IObservable<(TFirst First, TSecond Second)> WithLatestFrom(IObservable first, IObservable second) + { + return new WithLatestFrom(first, second, (t1, t2) => (t1, t2)); + } + + #endregion + + #region Zip + + public virtual IObservable<(TFirst First, TSecond Second)> Zip(IObservable first, IEnumerable second) + { + return new Zip.Enumerable(first, second, (t1, t2) => (t1, t2)); + } + + #endregion + + #region |> Helpers <| + + private static IObservable Combine(IObservable leftSource, IObservable rightSource, Func, IDisposable, IDisposable, IObserver, Notification>>> combinerSelector) + { + return new CombineObservable(leftSource, rightSource, combinerSelector); + } + + private sealed class CombineObservable : ObservableBase + { + private readonly IObservable _leftSource; + private readonly IObservable _rightSource; + private readonly Func, IDisposable, IDisposable, IObserver, Notification>>> _combinerSelector; + + public CombineObservable(IObservable leftSource, IObservable rightSource, Func, IDisposable, IDisposable, IObserver, Notification>>> combinerSelector) + { + _leftSource = leftSource; + _rightSource = rightSource; + _combinerSelector = combinerSelector; + } + + protected override IDisposable SubscribeCore(IObserver observer) + { + var leftSubscription = new SingleAssignmentDisposable(); + var rightSubscription = new SingleAssignmentDisposable(); + + var combiner = _combinerSelector(observer, leftSubscription, rightSubscription); + var gate = new object(); + + leftSubscription.Disposable = _leftSource.Materialize().Select(x => Either, Notification>.CreateLeft(x)).Synchronize(gate).Subscribe(combiner); + rightSubscription.Disposable = _rightSource.Materialize().Select(x => Either, Notification>.CreateRight(x)).Synchronize(gate).Subscribe(combiner); + + return StableCompositeDisposable.Create(leftSubscription, rightSubscription); + + } + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Linq/QueryLanguage_.cs b/LibExternal/System.Reactive/Linq/QueryLanguage_.cs new file mode 100644 index 0000000..cd7d04a --- /dev/null +++ b/LibExternal/System.Reactive/Linq/QueryLanguage_.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Linq +{ + internal partial class QueryLanguage : IQueryLanguage + { + } +} diff --git a/LibExternal/System.Reactive/ListObservable.cs b/LibExternal/System.Reactive/ListObservable.cs new file mode 100644 index 0000000..de19a92 --- /dev/null +++ b/LibExternal/System.Reactive/ListObservable.cs @@ -0,0 +1,210 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Collections.Generic; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Reactive.Subjects; + +namespace System.Reactive +{ + // CONSIDER: Deprecate this functionality or invest in an asynchronous variant. + + /// + /// Represents an object that retains the elements of the observable sequence and signals the end of the sequence. + /// + /// The type of elements received from the source sequence. + [Experimental] + public class ListObservable : IList, IObservable + { + private readonly IDisposable _subscription; + private readonly AsyncSubject _subject = new(); + private readonly List _results = new(); + + /// + /// Constructs an object that retains the values of source and signals the end of the sequence. + /// + /// The observable sequence whose elements will be retained in the list. + /// is null. + public ListObservable(IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + _subscription = source.Subscribe(_results.Add, _subject.OnError, _subject.OnCompleted); + } + + private void Wait() + { + _subject.DefaultIfEmpty().Wait(); + } + + /// + /// Returns the last value of the observable sequence. + /// + public T Value + { + get + { + Wait(); + + if (_results.Count == 0) + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + + return _results[_results.Count - 1]; + } + } + /// + /// Determines the index of a specific item in the ListObservable. + /// + /// The element to determine the index for. + /// The index of the specified item in the list; -1 if not found. + public int IndexOf(T item) + { + Wait(); + return _results.IndexOf(item); + } + + /// + /// Inserts an item to the ListObservable at the specified index. + /// + /// The index to insert the item at. + /// The item to insert in the list. + public void Insert(int index, T item) + { + Wait(); + _results.Insert(index, item); + } + + /// + /// Removes the ListObservable item at the specified index. + /// + /// The index of the item to remove. + public void RemoveAt(int index) + { + Wait(); + _results.RemoveAt(index); + } + + /// + /// Gets or sets the element at the specified index. + /// + /// The index of the item to retrieve or set. + public T this[int index] + { + get + { + Wait(); + return _results[index]; + } + set + { + Wait(); + _results[index] = value; + } + } + + /// + /// Adds an item to the ListObservable. + /// + /// The item to add to the list. + public void Add(T item) + { + Wait(); + _results.Add(item); + } + + /// + /// Removes all items from the ListObservable. + /// + public void Clear() + { + Wait(); + _results.Clear(); + } + + /// + /// Determines whether the ListObservable contains a specific value. + /// + /// The item to search for in the list. + /// true if found; false otherwise. + public bool Contains(T item) + { + Wait(); + return _results.Contains(item); + } + + /// + /// Copies the elements of the ListObservable to an System.Array, starting at a particular System.Array index. + /// + /// The array to copy elements to. + /// The start index in the array to start copying elements to. + public void CopyTo(T[] array, int arrayIndex) + { + Wait(); + _results.CopyTo(array, arrayIndex); + } + + /// + /// Gets the number of elements contained in the ListObservable. + /// + public int Count + { + get + { + Wait(); + return _results.Count; + } + } + + /// + /// Gets a value that indicates whether the ListObservable is read-only. + /// + public bool IsReadOnly => false; + + /// + /// Removes the first occurrence of a specific object from the ListObservable. + /// + /// The item to remove from the list. + /// true if the item was found; false otherwise. + public bool Remove(T item) + { + Wait(); + return _results.Remove(item); + } + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// Enumerator over the list. + public IEnumerator GetEnumerator() + { + Wait(); + return _results.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + /// Subscribes an observer to the ListObservable which will be notified upon completion. + /// + /// The observer to send completion or error messages to. + /// The disposable resource that can be used to unsubscribe. + /// is null. + public IDisposable Subscribe(IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + return StableCompositeDisposable.Create(_subscription, _subject.Subscribe(observer)); + } + } +} diff --git a/LibExternal/System.Reactive/NamespaceDocs.cs b/LibExternal/System.Reactive/NamespaceDocs.cs new file mode 100644 index 0000000..e8a22ee --- /dev/null +++ b/LibExternal/System.Reactive/NamespaceDocs.cs @@ -0,0 +1,88 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive +{ + /// + /// The System.Reactive namespace contains interfaces and classes used throughout the Reactive Extensions library. + /// + [Runtime.CompilerServices.CompilerGenerated] + internal class NamespaceDoc + { + } +} + +namespace System.Reactive.Concurrency +{ + /// + /// The System.Reactive.Concurrency namespace contains interfaces and classes that provide the scheduler infrastructure used by Reactive Extensions to construct and + /// process event streams. Schedulers are used to parameterize the concurrency introduced by query operators, provide means to virtualize time, to process historical data, + /// and to write unit tests for functionality built using Reactive Extensions constructs. + /// + [Runtime.CompilerServices.CompilerGenerated] + internal class NamespaceDoc + { + } +} + +namespace System.Reactive.Disposables +{ + /// + /// The System.Reactive.Disposables namespace contains interfaces and classes that provide a compositional set of constructs used to deal with resource and subscription + /// management in Reactive Extensions. Those types are used extensively within the implementation of Reactive Extensions and are useful when writing custom query operators or + /// schedulers. + /// + [Runtime.CompilerServices.CompilerGenerated] + internal class NamespaceDoc + { + } +} + +namespace System.Reactive.Linq +{ + /// + /// The System.Reactive.Linq namespace contains interfaces and classes that support expressing queries over observable sequences, using Language Integrated Query (LINQ). + /// Query operators are made available as extension methods for and defined on the Observable and Qbservable classes, respectively. + /// + [Runtime.CompilerServices.CompilerGenerated] + internal class NamespaceDoc + { + } +} + +namespace System.Reactive.Subjects +{ + /// + /// The System.Reactive.Subjects namespace contains interfaces and classes to represent subjects, which are objects implementing both and . + /// Subjects are often used as sources of events, allowing one party to raise events and allowing another party to write queries over the event stream. Because of their ability to + /// have multiple registered observers, subjects are also used as a facility to provide multicast behavior for event streams in queries. + /// + [Runtime.CompilerServices.CompilerGenerated] + internal class NamespaceDoc + { + } +} + +namespace System.Reactive.PlatformServices +{ + /// + /// The System.Reactive.PlatformServices namespace contains interfaces and classes used by the runtime infrastructure of Reactive Extensions. + /// Those are not intended to be used directly from user code and are subject to change in future releases of the product. + /// + [Runtime.CompilerServices.CompilerGenerated] + internal class NamespaceDoc + { + } +} + +namespace System.Reactive.Joins +{ + /// + /// The System.Reactive.Joins namespace contains classes used to express join patterns over observable sequences using fluent method syntax. + /// + [Runtime.CompilerServices.CompilerGenerated] + internal class NamespaceDoc + { + } +} diff --git a/LibExternal/System.Reactive/Notification.cs b/LibExternal/System.Reactive/Notification.cs new file mode 100644 index 0000000..32d8ace --- /dev/null +++ b/LibExternal/System.Reactive/Notification.cs @@ -0,0 +1,714 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Reactive.Concurrency; + +#pragma warning disable 0659 +#pragma warning disable 0661 + +namespace System.Reactive +{ + /// + /// Indicates the type of a notification. + /// + public enum NotificationKind + { + /// + /// Represents an OnNext notification. + /// + OnNext, + + /// + /// Represents an OnError notification. + /// + OnError, + + /// + /// Represents an OnCompleted notification. + /// + OnCompleted + } + + /// + /// Represents a notification to an observer. + /// + /// The type of the elements received by the observer. + [Serializable] + public abstract class Notification : IEquatable> + { + /// + /// Default constructor used by derived types. + /// + protected internal Notification() + { + } + + /// + /// Returns the value of an OnNext notification or throws an exception. + /// + public abstract T Value { get; } + + /// + /// Returns a value that indicates whether the notification has a value. + /// + public abstract bool HasValue { get; } + + /// + /// Returns the exception of an OnError notification or returns null. + /// + public abstract Exception? Exception { get; } + + /// + /// Gets the kind of notification that is represented. + /// + public abstract NotificationKind Kind { get; } + + /// + /// Represents an OnNext notification to an observer. + /// + [DebuggerDisplay("OnNext({Value})")] + [Serializable] + internal sealed class OnNextNotification : Notification + { + /// + /// Constructs a notification of a new value. + /// + public OnNextNotification(T value) + { + Value = value; + } + + /// + /// Returns the value of an OnNext notification. + /// + public override T Value { get; } + + /// + /// Returns null. + /// + public override Exception? Exception => null; + + /// + /// Returns true. + /// + public override bool HasValue => true; + + /// + /// Returns . + /// + public override NotificationKind Kind => NotificationKind.OnNext; + + /// + /// Returns the hash code for this instance. + /// + public override int GetHashCode() => Value?.GetHashCode() ?? 0; + + /// + /// Indicates whether this instance and a specified object are equal. + /// + public override bool Equals(Notification? other) + { + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other is null) + { + return false; + } + + if (other.Kind != NotificationKind.OnNext) + { + return false; + } + + return EqualityComparer.Default.Equals(Value, other.Value); + } + + /// + /// Returns a string representation of this instance. + /// + public override string ToString() => string.Format(CultureInfo.CurrentCulture, "OnNext({0})", Value); + + /// + /// Invokes the observer's method corresponding to the notification. + /// + /// Observer to invoke the notification on. + public override void Accept(IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + observer.OnNext(Value); + } + + /// + /// Invokes the observer's method corresponding to the notification and returns the produced result. + /// + /// Observer to invoke the notification on. + /// Result produced by the observation. + public override TResult Accept(IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + return observer.OnNext(Value); + } + + /// + /// Invokes the delegate corresponding to the notification. + /// + /// Delegate to invoke for an OnNext notification. + /// Delegate to invoke for an OnError notification. + /// Delegate to invoke for an OnCompleted notification. + public override void Accept(Action onNext, Action onError, Action onCompleted) + { + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + if (onError == null) + { + throw new ArgumentNullException(nameof(onError)); + } + + if (onCompleted == null) + { + throw new ArgumentNullException(nameof(onCompleted)); + } + + onNext(Value); + } + + /// + /// Invokes the delegate corresponding to the notification and returns the produced result. + /// + /// Delegate to invoke for an OnNext notification. + /// Delegate to invoke for an OnError notification. + /// Delegate to invoke for an OnCompleted notification. + /// Result produced by the observation. + public override TResult Accept(Func onNext, Func onError, Func onCompleted) + { + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + if (onError == null) + { + throw new ArgumentNullException(nameof(onError)); + } + + if (onCompleted == null) + { + throw new ArgumentNullException(nameof(onCompleted)); + } + + return onNext(Value); + } + } + + /// + /// Represents an OnError notification to an observer. + /// + [DebuggerDisplay("OnError({Exception})")] + [Serializable] + internal sealed class OnErrorNotification : Notification + { + /// + /// Constructs a notification of an exception. + /// + public OnErrorNotification(Exception exception) + { + Exception = exception; + } + + /// + /// Throws the exception. + /// + public override T Value { get { Exception.Throw(); return default!; } } + + /// + /// Returns the exception. + /// + public override Exception Exception { get; } + + /// + /// Returns false. + /// + public override bool HasValue => false; + + /// + /// Returns . + /// + public override NotificationKind Kind => NotificationKind.OnError; + + /// + /// Returns the hash code for this instance. + /// + public override int GetHashCode() => Exception.GetHashCode(); + + /// + /// Indicates whether this instance and other are equal. + /// + public override bool Equals(Notification? other) + { + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other is null) + { + return false; + } + + if (other.Kind != NotificationKind.OnError) + { + return false; + } + + return Equals(Exception, other.Exception); + } + + /// + /// Returns a string representation of this instance. + /// + public override string ToString() => string.Format(CultureInfo.CurrentCulture, "OnError({0})", Exception.GetType().FullName); + + /// + /// Invokes the observer's method corresponding to the notification. + /// + /// Observer to invoke the notification on. + public override void Accept(IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + observer.OnError(Exception); + } + + /// + /// Invokes the observer's method corresponding to the notification and returns the produced result. + /// + /// Observer to invoke the notification on. + /// Result produced by the observation. + public override TResult Accept(IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + return observer.OnError(Exception); + } + + /// + /// Invokes the delegate corresponding to the notification. + /// + /// Delegate to invoke for an OnNext notification. + /// Delegate to invoke for an OnError notification. + /// Delegate to invoke for an OnCompleted notification. + public override void Accept(Action onNext, Action onError, Action onCompleted) + { + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + if (onError == null) + { + throw new ArgumentNullException(nameof(onError)); + } + + if (onCompleted == null) + { + throw new ArgumentNullException(nameof(onCompleted)); + } + + onError(Exception); + } + + /// + /// Invokes the delegate corresponding to the notification and returns the produced result. + /// + /// Delegate to invoke for an OnNext notification. + /// Delegate to invoke for an OnError notification. + /// Delegate to invoke for an OnCompleted notification. + /// Result produced by the observation. + public override TResult Accept(Func onNext, Func onError, Func onCompleted) + { + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + if (onError == null) + { + throw new ArgumentNullException(nameof(onError)); + } + + if (onCompleted == null) + { + throw new ArgumentNullException(nameof(onCompleted)); + } + + return onError(Exception); + } + } + + /// + /// Represents an OnCompleted notification to an observer. + /// + [DebuggerDisplay("OnCompleted()")] + [Serializable] + internal sealed class OnCompletedNotification : Notification + { + /// + /// Complete notifications are stateless thus only one instance + /// can ever exist per type. + /// + internal static readonly Notification Instance = new OnCompletedNotification(); + + /// + /// Constructs a notification of the end of a sequence. + /// + private OnCompletedNotification() + { + } + + /// + /// Throws an . + /// + public override T Value { get { throw new InvalidOperationException(Strings_Core.COMPLETED_NO_VALUE); } } + + /// + /// Returns null. + /// + public override Exception? Exception => null; + + /// + /// Returns false. + /// + public override bool HasValue => false; + + /// + /// Returns . + /// + public override NotificationKind Kind => NotificationKind.OnCompleted; + + /// + /// Returns the hash code for this instance. + /// + public override int GetHashCode() => typeof(T).GetHashCode() ^ 8510; + + /// + /// Indicates whether this instance and other are equal. + /// + public override bool Equals(Notification? other) + { + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other is null) + { + return false; + } + + return other.Kind == NotificationKind.OnCompleted; + } + + /// + /// Returns a string representation of this instance. + /// + public override string ToString() => "OnCompleted()"; + + /// + /// Invokes the observer's method corresponding to the notification. + /// + /// Observer to invoke the notification on. + public override void Accept(IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + observer.OnCompleted(); + } + + /// + /// Invokes the observer's method corresponding to the notification and returns the produced result. + /// + /// Observer to invoke the notification on. + /// Result produced by the observation. + public override TResult Accept(IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + return observer.OnCompleted(); + } + + /// + /// Invokes the delegate corresponding to the notification. + /// + /// Delegate to invoke for an OnNext notification. + /// Delegate to invoke for an OnError notification. + /// Delegate to invoke for an OnCompleted notification. + public override void Accept(Action onNext, Action onError, Action onCompleted) + { + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + if (onError == null) + { + throw new ArgumentNullException(nameof(onError)); + } + + if (onCompleted == null) + { + throw new ArgumentNullException(nameof(onCompleted)); + } + + onCompleted(); + } + + /// + /// Invokes the delegate corresponding to the notification and returns the produced result. + /// + /// Delegate to invoke for an OnNext notification. + /// Delegate to invoke for an OnError notification. + /// Delegate to invoke for an OnCompleted notification. + /// Result produced by the observation. + public override TResult Accept(Func onNext, Func onError, Func onCompleted) + { + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + if (onError == null) + { + throw new ArgumentNullException(nameof(onError)); + } + + if (onCompleted == null) + { + throw new ArgumentNullException(nameof(onCompleted)); + } + + return onCompleted(); + } + } + + /// + /// Determines whether the current object has the same observer message payload as a specified value. + /// + /// An object to compare to the current object. + /// true if both objects have the same observer message payload; otherwise, false. + /// + /// Equality of objects is based on the equality of the observer message payload they represent, including the notification Kind and the Value or Exception (if any). + /// This means two objects can be equal even though they don't represent the same observer method call, but have the same Kind and have equal parameters passed to the observer method. + /// In case one wants to determine whether two objects represent the same observer method call, use Object.ReferenceEquals identity equality instead. + /// + public abstract bool Equals(Notification? other); + + /// + /// Determines whether the two specified objects have the same observer message payload. + /// + /// The first to compare, or null. + /// The second to compare, or null. + /// true if the first value has the same observer message payload as the second value; otherwise, false. + /// + /// Equality of objects is based on the equality of the observer message payload they represent, including the notification Kind and the Value or Exception (if any). + /// This means two objects can be equal even though they don't represent the same observer method call, but have the same Kind and have equal parameters passed to the observer method. + /// In case one wants to determine whether two objects represent the same observer method call, use Object.ReferenceEquals identity equality instead. + /// + public static bool operator ==(Notification left, Notification right) + { + if (ReferenceEquals(left, right)) + { + return true; + } + + if (left is null || right is null) + { + return false; + } + + return left.Equals(right); + } + + /// + /// Determines whether the two specified objects have a different observer message payload. + /// + /// The first to compare, or null. + /// The second to compare, or null. + /// true if the first value has a different observer message payload as the second value; otherwise, false. + /// + /// Equality of objects is based on the equality of the observer message payload they represent, including the notification Kind and the Value or Exception (if any). + /// This means two objects can be equal even though they don't represent the same observer method call, but have the same Kind and have equal parameters passed to the observer method. + /// In case one wants to determine whether two objects represent a different observer method call, use Object.ReferenceEquals identity equality instead. + /// + public static bool operator !=(Notification left, Notification right) => !(left == right); + + /// + /// Determines whether the specified System.Object is equal to the current . + /// + /// The System.Object to compare with the current . + /// true if the specified System.Object is equal to the current ; otherwise, false. + /// + /// Equality of objects is based on the equality of the observer message payload they represent, including the notification Kind and the Value or Exception (if any). + /// This means two objects can be equal even though they don't represent the same observer method call, but have the same Kind and have equal parameters passed to the observer method. + /// In case one wants to determine whether two objects represent the same observer method call, use Object.ReferenceEquals identity equality instead. + /// + public override bool Equals(object? obj) => Equals(obj as Notification); + + /// + /// Invokes the observer's method corresponding to the notification. + /// + /// Observer to invoke the notification on. + public abstract void Accept(IObserver observer); + + /// + /// Invokes the observer's method corresponding to the notification and returns the produced result. + /// + /// The type of the result returned from the observer's notification handlers. + /// Observer to invoke the notification on. + /// Result produced by the observation. + public abstract TResult Accept(IObserver observer); + + /// + /// Invokes the delegate corresponding to the notification. + /// + /// Delegate to invoke for an OnNext notification. + /// Delegate to invoke for an OnError notification. + /// Delegate to invoke for an OnCompleted notification. + public abstract void Accept(Action onNext, Action onError, Action onCompleted); + + /// + /// Invokes the delegate corresponding to the notification and returns the produced result. + /// + /// The type of the result returned from the notification handler delegates. + /// Delegate to invoke for an OnNext notification. + /// Delegate to invoke for an OnError notification. + /// Delegate to invoke for an OnCompleted notification. + /// Result produced by the observation. + public abstract TResult Accept(Func onNext, Func onError, Func onCompleted); + + /// + /// Returns an observable sequence with a single notification, using the immediate scheduler. + /// + /// The observable sequence that surfaces the behavior of the notification upon subscription. + public IObservable ToObservable() => ToObservable(ImmediateScheduler.Instance); + + /// + /// Returns an observable sequence with a single notification. + /// + /// Scheduler to send out the notification calls on. + /// The observable sequence that surfaces the behavior of the notification upon subscription. + public IObservable ToObservable(IScheduler scheduler) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return new NotificationToObservable(scheduler, this); + } + + private sealed class NotificationToObservable : ObservableBase + { + private readonly IScheduler _scheduler; + private readonly Notification _parent; + + public NotificationToObservable(IScheduler scheduler, Notification parent) + { + _scheduler = scheduler; + _parent = parent; + } + + protected override IDisposable SubscribeCore(IObserver observer) + { + return _scheduler.ScheduleAction((_parent, observer), state => + { + var parent = state._parent; + var o = state.observer; + + parent.Accept(o); + + if (parent.Kind == NotificationKind.OnNext) + { + o.OnCompleted(); + } + }); + } + } + } + + /// + /// Provides a set of static methods for constructing notifications. + /// + public static class Notification + { + /// + /// Creates an object that represents an OnNext notification to an observer. + /// + /// The type of the elements received by the observer. Upon dematerialization of the notifications into an observable sequence, this type is used as the element type for the sequence. + /// The value contained in the notification. + /// The OnNext notification containing the value. + public static Notification CreateOnNext(T value) + { + return new Notification.OnNextNotification(value); + } + + /// + /// Creates an object that represents an OnError notification to an observer. + /// + /// The type of the elements received by the observer. Upon dematerialization of the notifications into an observable sequence, this type is used as the element type for the sequence. + /// The exception contained in the notification. + /// The OnError notification containing the exception. + /// is null. + public static Notification CreateOnError(Exception error) + { + if (error == null) + { + throw new ArgumentNullException(nameof(error)); + } + + return new Notification.OnErrorNotification(error); + } + + /// + /// Creates an object that represents an OnCompleted notification to an observer. + /// + /// The type of the elements received by the observer. Upon dematerialization of the notifications into an observable sequence, this type is used as the element type for the sequence. + /// The OnCompleted notification. + public static Notification CreateOnCompleted() + { + return Notification.OnCompletedNotification.Instance; + } + } +} + +#pragma warning restore 0659 +#pragma warning restore 0661 diff --git a/LibExternal/System.Reactive/Observable.Extensions.cs b/LibExternal/System.Reactive/Observable.Extensions.cs new file mode 100644 index 0000000..1f2200f --- /dev/null +++ b/LibExternal/System.Reactive/Observable.Extensions.cs @@ -0,0 +1,429 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Reactive; +using System.Reactive.Disposables; +using System.Threading; + +namespace System +{ + /// + /// Provides a set of static methods for subscribing delegates to observables. + /// + public static class ObservableExtensions + { + #region Subscribe delegate-based overloads + + /// + /// Subscribes to the observable sequence without specifying any handlers. + /// This method can be used to evaluate the observable sequence for its side-effects only. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to subscribe to. + /// object used to unsubscribe from the observable sequence. + /// is null. + public static IDisposable Subscribe(this IObservable source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + // + // [OK] Use of unsafe Subscribe: non-pretentious constructor for an observer; this overload is not to be used internally. + // + return source.Subscribe/*Unsafe*/(new AnonymousObserver(Stubs.Ignore, Stubs.Throw, Stubs.Nop)); + } + + /// + /// Subscribes an element handler to an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to subscribe to. + /// Action to invoke for each element in the observable sequence. + /// object used to unsubscribe from the observable sequence. + /// or is null. + public static IDisposable Subscribe(this IObservable source, Action onNext) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + // + // [OK] Use of unsafe Subscribe: non-pretentious constructor for an observer; this overload is not to be used internally. + // + return source.Subscribe/*Unsafe*/(new AnonymousObserver(onNext, Stubs.Throw, Stubs.Nop)); + } + + /// + /// Subscribes an element handler and an exception handler to an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to subscribe to. + /// Action to invoke for each element in the observable sequence. + /// Action to invoke upon exceptional termination of the observable sequence. + /// object used to unsubscribe from the observable sequence. + /// or or is null. + public static IDisposable Subscribe(this IObservable source, Action onNext, Action onError) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + if (onError == null) + { + throw new ArgumentNullException(nameof(onError)); + } + + // + // [OK] Use of unsafe Subscribe: non-pretentious constructor for an observer; this overload is not to be used internally. + // + return source.Subscribe/*Unsafe*/(new AnonymousObserver(onNext, onError, Stubs.Nop)); + } + + /// + /// Subscribes an element handler and a completion handler to an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to subscribe to. + /// Action to invoke for each element in the observable sequence. + /// Action to invoke upon graceful termination of the observable sequence. + /// object used to unsubscribe from the observable sequence. + /// or or is null. + public static IDisposable Subscribe(this IObservable source, Action onNext, Action onCompleted) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + if (onCompleted == null) + { + throw new ArgumentNullException(nameof(onCompleted)); + } + + // + // [OK] Use of unsafe Subscribe: non-pretentious constructor for an observer; this overload is not to be used internally. + // + return source.Subscribe/*Unsafe*/(new AnonymousObserver(onNext, Stubs.Throw, onCompleted)); + } + + /// + /// Subscribes an element handler, an exception handler, and a completion handler to an observable sequence. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to subscribe to. + /// Action to invoke for each element in the observable sequence. + /// Action to invoke upon exceptional termination of the observable sequence. + /// Action to invoke upon graceful termination of the observable sequence. + /// object used to unsubscribe from the observable sequence. + /// or or or is null. + public static IDisposable Subscribe(this IObservable source, Action onNext, Action onError, Action onCompleted) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + if (onError == null) + { + throw new ArgumentNullException(nameof(onError)); + } + + if (onCompleted == null) + { + throw new ArgumentNullException(nameof(onCompleted)); + } + + // + // [OK] Use of unsafe Subscribe: non-pretentious constructor for an observer; this overload is not to be used internally. + // + return source.Subscribe/*Unsafe*/(new AnonymousObserver(onNext, onError, onCompleted)); + } + + #endregion + + #region Subscribe overloads with CancellationToken + + /// + /// Subscribes an observer to an observable sequence, using a to support unsubscription. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to subscribe to. + /// Observer to subscribe to the sequence. + /// CancellationToken that can be signaled to unsubscribe from the source sequence. + /// or is null. + public static void Subscribe(this IObservable source, IObserver observer, CancellationToken token) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + source.Subscribe_(observer, token); + } + + /// + /// Subscribes to the observable sequence without specifying any handlers, using a to support unsubscription. + /// This method can be used to evaluate the observable sequence for its side-effects only. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to subscribe to. + /// CancellationToken that can be signaled to unsubscribe from the source sequence. + /// is null. + public static void Subscribe(this IObservable source, CancellationToken token) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + source.Subscribe_(new AnonymousObserver(Stubs.Ignore, Stubs.Throw, Stubs.Nop), token); + } + + /// + /// Subscribes an element handler to an observable sequence, using a to support unsubscription. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to subscribe to. + /// Action to invoke for each element in the observable sequence. + /// CancellationToken that can be signaled to unsubscribe from the source sequence. + /// or is null. + public static void Subscribe(this IObservable source, Action onNext, CancellationToken token) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + source.Subscribe_(new AnonymousObserver(onNext, Stubs.Throw, Stubs.Nop), token); + } + + /// + /// Subscribes an element handler and an exception handler to an observable sequence, using a to support unsubscription. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to subscribe to. + /// Action to invoke for each element in the observable sequence. + /// Action to invoke upon exceptional termination of the observable sequence. + /// CancellationToken that can be signaled to unsubscribe from the source sequence. + /// or or is null. + public static void Subscribe(this IObservable source, Action onNext, Action onError, CancellationToken token) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + if (onError == null) + { + throw new ArgumentNullException(nameof(onError)); + } + + source.Subscribe_(new AnonymousObserver(onNext, onError, Stubs.Nop), token); + } + + /// + /// Subscribes an element handler and a completion handler to an observable sequence, using a to support unsubscription. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to subscribe to. + /// Action to invoke for each element in the observable sequence. + /// Action to invoke upon graceful termination of the observable sequence. + /// CancellationToken that can be signaled to unsubscribe from the source sequence. + /// or or is null. + public static void Subscribe(this IObservable source, Action onNext, Action onCompleted, CancellationToken token) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + if (onCompleted == null) + { + throw new ArgumentNullException(nameof(onCompleted)); + } + + source.Subscribe_(new AnonymousObserver(onNext, Stubs.Throw, onCompleted), token); + } + + /// + /// Subscribes an element handler, an exception handler, and a completion handler to an observable sequence, using a to support unsubscription. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to subscribe to. + /// Action to invoke for each element in the observable sequence. + /// Action to invoke upon exceptional termination of the observable sequence. + /// Action to invoke upon graceful termination of the observable sequence. + /// CancellationToken that can be signaled to unsubscribe from the source sequence. + /// or or or is null. + public static void Subscribe(this IObservable source, Action onNext, Action onError, Action onCompleted, CancellationToken token) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + if (onError == null) + { + throw new ArgumentNullException(nameof(onError)); + } + + if (onCompleted == null) + { + throw new ArgumentNullException(nameof(onCompleted)); + } + + source.Subscribe_(new AnonymousObserver(onNext, onError, onCompleted), token); + } + + private static void Subscribe_(this IObservable source, IObserver observer, CancellationToken token) + { + if (token.CanBeCanceled) + { + if (!token.IsCancellationRequested) + { + var consumer = SafeObserver.Wrap(observer); + + // + // [OK] Use of unsafe Subscribe: exception during Subscribe doesn't orphan CancellationTokenRegistration. + // + var d = source.Subscribe/*Unsafe*/(consumer); + + consumer.SetResource(token.Register(state => ((IDisposable)state!).Dispose(), d)); + } + } + else + { + source.Subscribe(observer); + } + } + + #endregion + + #region SubscribeSafe + + /// + /// Subscribes to the specified source, re-routing synchronous exceptions during invocation of the method to the observer's channel. + /// This method is typically used when writing query operators. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to subscribe to. + /// Observer that will be passed to the observable sequence, and that will be used for exception propagation. + /// object used to unsubscribe from the observable sequence. + /// or is null. + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static IDisposable SubscribeSafe(this IObservable source, IObserver observer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + // + // The following types are white-listed and should not exhibit exceptional behavior + // for regular operation circumstances. + // + if (source is ObservableBase) + { + return source.Subscribe(observer); + } + + if (source is IProducer producer) + { + return producer.SubscribeRaw(observer, enableSafeguard: false); + } + + var d = Disposable.Empty; + + try + { + d = source.Subscribe(observer); + } + catch (Exception exception) + { + // + // The effect of redirecting the exception to the OnError channel is automatic + // clean-up of query operator state for a large number of cases. For example, + // consider a binary and temporal query operator with the following Subscribe + // behavior (implemented using the Producer pattern with a Run method): + // + // public IDisposable Run(...) + // { + // var tm = _scheduler.Schedule(_due, Tick); + // + // var df = _fst.SubscribeSafe(new FstObserver(this, ...)); + // var ds = _snd.SubscribeSafe(new SndObserver(this, ...)); // <-- fails + // + // return new CompositeDisposable(tm, df, ds); + // } + // + // If the second subscription fails, we're not leaving the first subscription + // or the scheduled job hanging around. Instead, the OnError propagation to + // the SndObserver should take care of a Dispose call to the observer's parent + // object. The handshake between Producer and Sink objects will ultimately + // cause disposal of the CompositeDisposable that's returned from the method. + // + observer.OnError(exception); + } + + return d; + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/ObservableBase.cs b/LibExternal/System.Reactive/ObservableBase.cs new file mode 100644 index 0000000..f1f0548 --- /dev/null +++ b/LibExternal/System.Reactive/ObservableBase.cs @@ -0,0 +1,112 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; + +namespace System.Reactive +{ + /// + /// Abstract base class for implementations of the interface. + /// + /// + /// If you don't need a named type to create an observable sequence (i.e. you rather need + /// an instance rather than a reusable type), use the Observable.Create method to create + /// an observable sequence with specified subscription behavior. + /// + /// The type of the elements in the sequence. + public abstract class ObservableBase : IObservable + { + /// + /// Subscribes the given observer to the observable sequence. + /// + /// Observer that will receive notifications from the observable sequence. + /// Disposable object representing an observer's subscription to the observable sequence. + /// is null. + public IDisposable Subscribe(IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + var autoDetachObserver = new AutoDetachObserver(observer); + + if (CurrentThreadScheduler.IsScheduleRequired) + { + // + // Notice we don't protect this piece of code using an exception handler to + // redirect errors to the OnError channel. This call to Schedule will run the + // trampoline, so we'd be catching all exceptions, including those from user + // callbacks that happen to run there. For example, consider: + // + // Observable.Return(42, Scheduler.CurrentThread) + // .Subscribe(x => { throw new Exception(); }); + // + // Here, the OnNext(42) call would be scheduled on the trampoline, so when we + // return from the scheduled Subscribe call, the CurrentThreadScheduler moves + // on to invoking this work item. Too much of protection here would cause the + // exception thrown in OnNext to circle back to OnError, which looks like the + // sequence can't make up its mind. + // + CurrentThreadScheduler.Instance.ScheduleAction(autoDetachObserver, ScheduledSubscribe); + } + else + { + try + { + autoDetachObserver.SetResource(SubscribeCore(autoDetachObserver)); + } + catch (Exception exception) + { + // + // This can happen when there's a synchronous callback to OnError in the + // implementation of SubscribeCore, which also throws. So, we're seeing + // an exception being thrown from a handler. + // + // For compat with v1.x, we rethrow the exception in this case, keeping + // in mind this should be rare but if it happens, something's totally + // screwed up. + // + if (!autoDetachObserver.Fail(exception)) + { + throw; + } + } + } + + return autoDetachObserver; + } + + private void ScheduledSubscribe(AutoDetachObserver autoDetachObserver) + { + try + { + autoDetachObserver.SetResource(SubscribeCore(autoDetachObserver)); + } + catch (Exception exception) + { + // + // This can happen when there's a synchronous callback to OnError in the + // implementation of SubscribeCore, which also throws. So, we're seeing + // an exception being thrown from a handler. + // + // For compat with v1.x, we rethrow the exception in this case, keeping + // in mind this should be rare but if it happens, something's totally + // screwed up. + // + if (!autoDetachObserver.Fail(exception)) + { + throw; + } + } + } + + /// + /// Implement this method with the core subscription logic for the observable sequence. + /// + /// Observer to send notifications to. + /// Disposable object representing an observer's subscription to the observable sequence. + protected abstract IDisposable SubscribeCore(IObserver observer); + } +} diff --git a/LibExternal/System.Reactive/ObservableQuery.cs b/LibExternal/System.Reactive/ObservableQuery.cs new file mode 100644 index 0000000..abe1629 --- /dev/null +++ b/LibExternal/System.Reactive/ObservableQuery.cs @@ -0,0 +1,512 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Reactive.Joins; +using System.Reactive.Linq; +using System.Reflection; + +namespace System.Reactive +{ +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.AsQueryableTrimIncompatibilityMessage)] +#endif + internal class ObservableQueryProvider : IQbservableProvider, IQueryProvider + { + public IQbservable CreateQuery(Expression expression) + { + if (expression == null) + { + throw new ArgumentNullException(nameof(expression)); + } + + if (!typeof(IObservable).IsAssignableFrom(expression.Type)) + { + throw new ArgumentException(Strings_Providers.INVALID_TREE_TYPE, nameof(expression)); + } + + return new ObservableQuery(expression); + } + + IQueryable IQueryProvider.CreateQuery(Expression expression) + { + // + // Here we're on the edge between IQbservable and IQueryable for the local + // execution case. E.g.: + // + // observable.AsQbservable()..ToQueryable() + // + // This should be turned into a local execution, with the push-to-pull + // adapter in the middle, so we rewrite to: + // + // observable.AsQbservable()..ToEnumerable().AsQueryable() + // + if (expression is not MethodCallExpression call || + call.Method.DeclaringType != typeof(Qbservable) || + call.Method.Name != nameof(Qbservable.ToQueryable)) + { + throw new ArgumentException(Strings_Providers.EXPECTED_TOQUERYABLE_METHODCALL, nameof(expression)); + } + + // + // This is the IQbservable object corresponding to the lhs. Now wrap + // it in two calls to get the local queryable. + // + var arg0 = call.Arguments[0]; + var res = + Expression.Call( + AsQueryable.MakeGenericMethod(typeof(TElement)), + Expression.Call( + typeof(Observable).GetMethod(nameof(Observable.ToEnumerable))!.MakeGenericMethod(typeof(TElement)), + arg0 + ) + ); + + // + // Queryable operator calls should be taken care of by the provider for + // LINQ to Objects. So we compile and get the resulting IQueryable + // back to hand it out. + // + return Expression.Lambda>>(res).Compile()(); + } + + private static MethodInfo? _staticAsQueryable; + + private static MethodInfo AsQueryable => _staticAsQueryable ??= Qbservable.InfoOf(() => Queryable.AsQueryable(null!)).GetGenericMethodDefinition(); + + IQueryable IQueryProvider.CreateQuery(Expression expression) + { + throw new NotImplementedException(); + } + + TResult IQueryProvider.Execute(Expression expression) + { + throw new NotImplementedException(); + } + + object IQueryProvider.Execute(Expression expression) + { + throw new NotImplementedException(); + } + } + +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.AsQueryableTrimIncompatibilityMessage)] +#endif + internal class ObservableQuery + { + protected object? _source; + protected Expression _expression; + + public ObservableQuery(object source) + { + _source = source; + _expression = Expression.Constant(this); + } + + public ObservableQuery(Expression expression) + { + _expression = expression; + } + + public object? Source => _source; + + public Expression Expression => _expression; + } + +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.AsQueryableTrimIncompatibilityMessage)] +#endif + internal class ObservableQuery : ObservableQuery, IQbservable + { + internal ObservableQuery(IObservable source) + : base(source) + { + } + + internal ObservableQuery(Expression expression) + : base(expression) + { + } + + public Type ElementType => typeof(TSource); + + public IQbservableProvider Provider => Qbservable.Provider; + + public IDisposable Subscribe(IObserver observer) + { + if (_source == null) + { + var rewriter = new ObservableRewriter(); + var body = rewriter.Visit(_expression); + var f = Expression.Lambda>>(body); + _source = f.Compile()(); + } + + // + // [OK] Use of unsafe Subscribe: non-pretentious mapping to IObservable behavior equivalent to the expression tree. + // + return ((IObservable)_source).Subscribe/*Unsafe*/(observer); + } + + public override string? ToString() + { + if (_expression is ConstantExpression c && c.Value == this) + { + if (_source != null) + { + return _source.ToString(); + } + + return "null"; + } + + return _expression.ToString(); + } + +#if HAS_TRIMMABILITY_ATTRIBUTES + [RequiresUnreferencedCode(Constants_Core.AsQueryableTrimIncompatibilityMessage)] +#endif + private class ObservableRewriter : ExpressionVisitor + { + protected override Expression VisitConstant(ConstantExpression/*!*/ node) + { + if (node.Value is ObservableQuery query) + { + var source = query.Source; + if (source != null) + { + return Expression.Constant(source); + } + + return Visit(query.Expression); + } + + return node; + } + + protected override Expression VisitMethodCall(MethodCallExpression/*!*/ node) + { + var method = node.Method; + var declaringType = method.DeclaringType; + var baseType = declaringType?.BaseType; + if (baseType == typeof(QueryablePattern)) + { + if (method.Name == "Then") + { + // + // Retarget Then to the corresponding pattern. Recursive visit of the lhs will rewrite + // the chain of And operators. + // + var pattern = Visit(node.Object); + var arguments = node.Arguments.Select(arg => Unquote(Visit(arg))).ToArray(); + var then = Expression.Call(pattern!, method.Name, method.GetGenericArguments(), arguments); + return then; + } + + if (method.Name == "And") + { + // + // Retarget And to the corresponding pattern. + // + var lhs = Visit(node.Object); + var arguments = node.Arguments.Select(arg => Visit(arg)).ToArray(); + var and = Expression.Call(lhs!, method.Name, method.GetGenericArguments(), arguments); + return and; + } + } + else + { + var arguments = node.Arguments.AsEnumerable(); + + // + // Checking for an IQbservable operator, being either: + // - an extension method on IQbservableProvider + // - an extension method on IQbservable + // + var isOperator = false; + + var firstParameter = method.GetParameters().FirstOrDefault(); + if (firstParameter != null) + { + var firstParameterType = firstParameter.ParameterType; + + // + // Operators like Qbservable.Amb have an n-ary form that take in an IQbservableProvider + // as the first argument. In such a case we need to make sure that the given provider is + // the one targeting regular Observable. If not, we keep the subtree as-is and let that + // provider handle the execution. + // + if (firstParameterType == typeof(IQbservableProvider)) + { + isOperator = true; + + // + // Since we could be inside a lambda expression where one tries to obtain a query + // provider, or that provider could be stored in an outer variable, we need to + // evaluate the expression to obtain an IQbservableProvider object. + // + var provider = Expression.Lambda>(Visit(node.Arguments[0])).Compile()(); + + // + // Let's see whether the ObservableQuery provider is targeted. This one always goes + // to local execution. E.g.: + // + // var xs = Observable.Return(1).AsQbservable(); + // var ys = Observable.Return(2).AsQbservable(); + // var zs = Observable.Return(3).AsQbservable(); + // + // var res = Qbservable.Provider.Amb(xs, ys, zs); + // ^^^^^^^^^^^^^^^^^^^ + // + if (provider is ObservableQueryProvider) + { + // + // For further rewrite, simply ignore the query provider argument now to match + // up with the Observable signature. E.g.: + // + // var res = Qbservable.Provider.Amb(xs, ys, zs); + // = Qbservable.Amb(Qbservable.Provider, xs, ys, zs)' + // ^^^^^^^^^^^^^^^^^^^ + // -> + // var res = Observable.Amb(xs, ys, zs); + // + arguments = arguments.Skip(1); + } + else + { + // + // We've hit an unknown provider and will defer further execution to it. Upon + // calling Subscribe to the node's output, that provider will take care of it. + // + return node; + } + } + else if (typeof(IQbservable).IsAssignableFrom(firstParameterType)) + { + isOperator = true; + } + } + + if (isOperator) + { + var args = VisitQbservableOperatorArguments(method, arguments); + return FindObservableMethod(method, args); + } + } + + return base.VisitMethodCall(node); + } + + protected override Expression VisitLambda(Expression node) + { + return node; + } + + private IList VisitQbservableOperatorArguments(MethodInfo method, IEnumerable arguments) + { + // + // Recognize the Qbservable.When(IQbservableProvider, QueryablePlan[]) + // overload in order to substitute the array with a Plan[]. + // + if (method.Name == "When") + { +#pragma warning disable CA1851 // (Possible multiple enumerations of 'IEnumerable'.) Simple fixes could actually make things worse (e.g., by making an unnecessary copy), so unless specific perf issues become apparent here, we will tolerate this. + var lastArgument = arguments.Last(); +#pragma warning restore CA1851 + if (lastArgument.NodeType == ExpressionType.NewArrayInit) + { + var paramsArray = (NewArrayExpression)lastArgument; + return new List + { + Expression.NewArrayInit( + typeof(Plan<>).MakeGenericType(method.GetGenericArguments()[0]), + paramsArray.Expressions.Select(param => Visit(param)) + ) + }; + } + } + +#pragma warning disable CA1851 // Possible multiple enumerations of 'IEnumerable' collection + return arguments.Select(arg => Visit(arg)).ToList(); +#pragma warning restore CA1851 + } + + private class Lazy + { + private readonly Func _factory; + private T? _value; + private bool _initialized; + + public Lazy(Func factory) + { + _factory = factory; + } + + public T Value + { + get + { + lock (_factory) + { + if (!_initialized) + { + _value = _factory(); + _initialized = true; + } + } + + return _value!; + } + } + } + + private static readonly Lazy> ObservableMethods = new(() => GetMethods(typeof(Observable))); + + private static MethodCallExpression FindObservableMethod(MethodInfo method, IList arguments) + { + // + // Where to look for the matching operator? + // + + Type targetType; + ILookup methods; + + if (method.DeclaringType == typeof(Qbservable)) + { + targetType = typeof(Observable); + methods = ObservableMethods.Value; + } + else + { + targetType = method.DeclaringType!; // NB: These methods were found from a declaring type. + + if (targetType.IsDefined(typeof(LocalQueryMethodImplementationTypeAttribute), false)) + { + var mapping = (LocalQueryMethodImplementationTypeAttribute)targetType.GetCustomAttributes(typeof(LocalQueryMethodImplementationTypeAttribute), false)[0]; + targetType = mapping.TargetType; + } + + methods = GetMethods(targetType); + } + + // + // From all the operators with the method's name, find the one that matches all arguments. + // + var typeArgs = method.IsGenericMethod ? method.GetGenericArguments() : null; + var targetMethod = methods[method.Name].FirstOrDefault(candidateMethod => ArgsMatch(candidateMethod, arguments, typeArgs)) + ?? throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings_Providers.NO_MATCHING_METHOD_FOUND, method.Name, targetType.Name)); + + // + // Restore generic arguments. + // + if (typeArgs != null) + { + targetMethod = targetMethod.MakeGenericMethod(typeArgs); + } + + // + // Finally, we need to deal with mismatches on Expression> versus Func<...>. + // + var parameters = targetMethod.GetParameters(); + for (int i = 0, n = parameters.Length; i < n; i++) + { + arguments[i] = Unquote(arguments[i]); + } + + // + // Emit a new call to the discovered target method. + // + return Expression.Call(null, targetMethod, arguments); + } + + private static ILookup GetMethods(Type type) + { + return type.GetTypeInfo().DeclaredMethods.Where(m => m.IsStatic && m.IsPublic).ToLookup(m => m.Name); + } + + private static bool ArgsMatch(MethodInfo method, IList arguments, Type[]? typeArgs) + { + // + // Number of parameters should match. Notice we've sanitized IQbservableProvider "this" + // parameters first (see VisitMethodCall). + // + var parameters = method.GetParameters(); + if (parameters.Length != arguments.Count) + { + return false; + } + + // + // Genericity should match too. + // + if (!method.IsGenericMethod && typeArgs != null && typeArgs.Length > 0) + { + return false; + } + + // + // Reconstruct the generic method if needed. + // + if (method.IsGenericMethodDefinition) + { + if (typeArgs == null) + { + return false; + } + + if (method.GetGenericArguments().Length != typeArgs.Length) + { + return false; + } + + var result = method.MakeGenericMethod(typeArgs); + parameters = result.GetParameters(); + } + + // + // Check compatibility for the parameter types. + // + for (int i = 0, n = arguments.Count; i < n; i++) + { + var parameterType = parameters[i].ParameterType; + var argument = arguments[i]; + + // + // For operators that take a function (like Where, Select), we'll be faced + // with a quoted argument and a discrepancy between Expression> + // and the underlying Func<...>. + // + if (!parameterType.IsAssignableFrom(argument.Type)) + { + argument = Unquote(argument); + if (!parameterType.IsAssignableFrom(argument.Type)) + { + return false; + } + } + } + + return true; + } + + private static Expression Unquote(Expression expression) + { + // + // Get rid of all outer quotes around an expression. + // + while (expression.NodeType == ExpressionType.Quote) + { + expression = ((UnaryExpression)expression).Operand; + } + + return expression; + } + } + } +} diff --git a/LibExternal/System.Reactive/Observer.Extensions.cs b/LibExternal/System.Reactive/Observer.Extensions.cs new file mode 100644 index 0000000..aed851d --- /dev/null +++ b/LibExternal/System.Reactive/Observer.Extensions.cs @@ -0,0 +1,405 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Threading; + +namespace System.Reactive +{ + /// + /// Provides a set of static methods for creating observers. + /// + public static class Observer + { + /// + /// Creates an observer from a notification callback. + /// + /// The type of the elements received by the observer. + /// Action that handles a notification. + /// The observer object that invokes the specified handler using a notification corresponding to each message it receives. + /// is null. + public static IObserver ToObserver(this Action> handler) + { + if (handler == null) + { + throw new ArgumentNullException(nameof(handler)); + } + + return new AnonymousObserver( + x => handler(Notification.CreateOnNext(x)), + exception => handler(Notification.CreateOnError(exception)), + () => handler(Notification.CreateOnCompleted()) + ); + } + + /// + /// Creates a notification callback from an observer. + /// + /// The type of the elements received by the observer. + /// Observer object. + /// The action that forwards its input notification to the underlying observer. + /// is null. + [Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Notifier", Justification = "Backward compat.")] + public static Action> ToNotifier(this IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + return n => n.Accept(observer); + } + + /// + /// Creates an observer from the specified OnNext action. + /// + /// The type of the elements received by the observer. + /// Observer's OnNext action implementation. + /// The observer object implemented using the given actions. + /// is null. + public static IObserver Create(Action onNext) + { + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + return new AnonymousObserver(onNext); + } + + /// + /// Creates an observer from the specified OnNext and OnError actions. + /// + /// The type of the elements received by the observer. + /// Observer's OnNext action implementation. + /// Observer's OnError action implementation. + /// The observer object implemented using the given actions. + /// or is null. + public static IObserver Create(Action onNext, Action onError) + { + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + if (onError == null) + { + throw new ArgumentNullException(nameof(onError)); + } + + return new AnonymousObserver(onNext, onError); + } + + /// + /// Creates an observer from the specified OnNext and OnCompleted actions. + /// + /// The type of the elements received by the observer. + /// Observer's OnNext action implementation. + /// Observer's OnCompleted action implementation. + /// The observer object implemented using the given actions. + /// or is null. + public static IObserver Create(Action onNext, Action onCompleted) + { + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + if (onCompleted == null) + { + throw new ArgumentNullException(nameof(onCompleted)); + } + + return new AnonymousObserver(onNext, onCompleted); + } + + /// + /// Creates an observer from the specified OnNext, OnError, and OnCompleted actions. + /// + /// The type of the elements received by the observer. + /// Observer's OnNext action implementation. + /// Observer's OnError action implementation. + /// Observer's OnCompleted action implementation. + /// The observer object implemented using the given actions. + /// or or is null. + public static IObserver Create(Action onNext, Action onError, Action onCompleted) + { + if (onNext == null) + { + throw new ArgumentNullException(nameof(onNext)); + } + + if (onError == null) + { + throw new ArgumentNullException(nameof(onError)); + } + + if (onCompleted == null) + { + throw new ArgumentNullException(nameof(onCompleted)); + } + + return new AnonymousObserver(onNext, onError, onCompleted); + } + + /// + /// Hides the identity of an observer. + /// + /// The type of the elements received by the source observer. + /// An observer whose identity to hide. + /// An observer that hides the identity of the specified observer. + /// is null. + public static IObserver AsObserver(this IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + return new AnonymousObserver(observer.OnNext, observer.OnError, observer.OnCompleted); + } + + /// + /// Checks access to the observer for grammar violations. This includes checking for multiple OnError or OnCompleted calls, as well as reentrancy in any of the observer methods. + /// If a violation is detected, an InvalidOperationException is thrown from the offending observer method call. + /// + /// The type of the elements received by the source observer. + /// The observer whose callback invocations should be checked for grammar violations. + /// An observer that checks callbacks invocations against the observer grammar and, if the checks pass, forwards those to the specified observer. + /// is null. + public static IObserver Checked(this IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + return new CheckedObserver(observer); + } + + /// + /// Synchronizes access to the observer such that its callback methods cannot be called concurrently from multiple threads. This overload is useful when coordinating access to an observer. + /// Notice reentrant observer callbacks on the same thread are still possible. + /// + /// The type of the elements received by the source observer. + /// The observer whose callbacks should be synchronized. + /// An observer that delivers callbacks to the specified observer in a synchronized manner. + /// is null. + /// + /// Because a Monitor is used to perform the synchronization, there's no protection against reentrancy from the same thread. + /// Hence, overlapped observer callbacks are still possible, which is invalid behavior according to the observer grammar. In order to protect against this behavior as + /// well, use the overload, passing true for the second parameter. + /// + public static IObserver Synchronize(IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + return new SynchronizedObserver(observer, new object()); + } + + /// + /// Synchronizes access to the observer such that its callback methods cannot be called concurrently. This overload is useful when coordinating access to an observer. + /// The parameter configures the type of lock used for synchronization. + /// + /// The type of the elements received by the source observer. + /// The observer whose callbacks should be synchronized. + /// If set to true, reentrant observer callbacks will be queued up and get delivered to the observer in a sequential manner. + /// An observer that delivers callbacks to the specified observer in a synchronized manner. + /// is null. + /// + /// When the parameter is set to false, behavior is identical to the overload which uses + /// a Monitor for synchronization. When the parameter is set to true, an + /// is used to queue up callbacks to the specified observer if a reentrant call is made. + /// + public static IObserver Synchronize(IObserver observer, bool preventReentrancy) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + if (preventReentrancy) + { + return new AsyncLockObserver(observer, new AsyncLock()); + } + + return new SynchronizedObserver(observer, new object()); + } + + /// + /// Synchronizes access to the observer such that its callback methods cannot be called concurrently by multiple threads, using the specified gate object for use by a Monitor-based lock. + /// This overload is useful when coordinating multiple observers that access shared state by synchronizing on a common gate object. + /// Notice reentrant observer callbacks on the same thread are still possible. + /// + /// The type of the elements received by the source observer. + /// The observer whose callbacks should be synchronized. + /// Gate object to synchronize each observer call on. + /// An observer that delivers callbacks to the specified observer in a synchronized manner. + /// or is null. + /// + /// Because a Monitor is used to perform the synchronization, there's no protection against reentrancy from the same thread. + /// Hence, overlapped observer callbacks are still possible, which is invalid behavior according to the observer grammar. In order to protect against this behavior as + /// well, use the overload. + /// + public static IObserver Synchronize(IObserver observer, object gate) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + if (gate == null) + { + throw new ArgumentNullException(nameof(gate)); + } + + return new SynchronizedObserver(observer, gate); + } + + /// + /// Synchronizes access to the observer such that its callback methods cannot be called concurrently, using the specified asynchronous lock to protect against concurrent and reentrant access. + /// This overload is useful when coordinating multiple observers that access shared state by synchronizing on a common asynchronous lock. + /// + /// The type of the elements received by the source observer. + /// The observer whose callbacks should be synchronized. + /// Gate object to synchronize each observer call on. + /// An observer that delivers callbacks to the specified observer in a synchronized manner. + /// or is null. + public static IObserver Synchronize(IObserver observer, AsyncLock asyncLock) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + if (asyncLock == null) + { + throw new ArgumentNullException(nameof(asyncLock)); + } + + return new AsyncLockObserver(observer, asyncLock); + } + + /// + /// Schedules the invocation of observer methods on the given scheduler. + /// + /// The type of the elements received by the source observer. + /// The observer to schedule messages for. + /// Scheduler to schedule observer messages on. + /// Observer whose messages are scheduled on the given scheduler. + /// or is null. + public static IObserver NotifyOn(this IObserver observer, IScheduler scheduler) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return new ObserveOnObserver(scheduler, observer); + } + + /// + /// Schedules the invocation of observer methods on the given synchronization context. + /// + /// The type of the elements received by the source observer. + /// The observer to schedule messages for. + /// Synchronization context to schedule observer messages on. + /// Observer whose messages are scheduled on the given synchronization context. + /// or is null. + public static IObserver NotifyOn(this IObserver observer, SynchronizationContext context) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + return new ObserveOnObserver(new SynchronizationContextScheduler(context), observer); + } + + /// + /// Converts an observer to a progress object. + /// + /// The type of the progress objects received by the source observer. + /// The observer to convert. + /// Progress object whose Report messages correspond to the observer's OnNext messages. + /// is null. + public static IProgress ToProgress(this IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + return new AnonymousProgress(observer.OnNext); + } + + /// + /// Converts an observer to a progress object, using the specified scheduler to invoke the progress reporting method. + /// + /// The type of the progress objects received by the source observer. + /// The observer to convert. + /// Scheduler to report progress on. + /// Progress object whose Report messages correspond to the observer's OnNext messages. + /// or is null. + public static IProgress ToProgress(this IObserver observer, IScheduler scheduler) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return new AnonymousProgress(new ObserveOnObserver(scheduler, observer).OnNext); + } + + private class AnonymousProgress : IProgress + { + private readonly Action _progress; + + public AnonymousProgress(Action progress) + { + _progress = progress; + } + + public void Report(T value) + { + _progress(value); + } + } + + /// + /// Converts a progress object to an observer. + /// + /// The type of the progress objects received by the progress reporter. + /// The progress object to convert. + /// Observer whose OnNext messages correspond to the progress object's Report messages. + /// is null. + public static IObserver ToObserver(this IProgress progress) + { + if (progress == null) + { + throw new ArgumentNullException(nameof(progress)); + } + + return new AnonymousObserver(progress.Report); + } + } +} diff --git a/LibExternal/System.Reactive/ObserverBase.cs b/LibExternal/System.Reactive/ObserverBase.cs new file mode 100644 index 0000000..d93d6dd --- /dev/null +++ b/LibExternal/System.Reactive/ObserverBase.cs @@ -0,0 +1,120 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Threading; + +namespace System.Reactive +{ + /// + /// Abstract base class for implementations of the interface. + /// + /// This base class enforces the grammar of observers where and are terminal messages. + /// The type of the elements in the sequence. + public abstract class ObserverBase : IObserver, IDisposable + { + private int _isStopped; + + /// + /// Creates a new observer in a non-stopped state. + /// + protected ObserverBase() + { + _isStopped = 0; + } + + /// + /// Notifies the observer of a new element in the sequence. + /// + /// Next element in the sequence. + public void OnNext(T value) + { + if (Volatile.Read(ref _isStopped) == 0) + { + OnNextCore(value); + } + } + + /// + /// Implement this method to react to the receival of a new element in the sequence. + /// + /// Next element in the sequence. + /// This method only gets called when the observer hasn't stopped yet. + protected abstract void OnNextCore(T value); + + /// + /// Notifies the observer that an exception has occurred. + /// + /// The error that has occurred. + /// is null. + public void OnError(Exception error) + { + if (error == null) + { + throw new ArgumentNullException(nameof(error)); + } + + if (Interlocked.Exchange(ref _isStopped, 1) == 0) + { + OnErrorCore(error); + } + } + + /// + /// Implement this method to react to the occurrence of an exception. + /// + /// The error that has occurred. + /// This method only gets called when the observer hasn't stopped yet, and causes the observer to stop. + [Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Error", Justification = "Same name as in the IObserver definition of OnError in the BCL.")] + protected abstract void OnErrorCore(Exception error); + + /// + /// Notifies the observer of the end of the sequence. + /// + public void OnCompleted() + { + if (Interlocked.Exchange(ref _isStopped, 1) == 0) + { + OnCompletedCore(); + } + } + + /// + /// Implement this method to react to the end of the sequence. + /// + /// This method only gets called when the observer hasn't stopped yet, and causes the observer to stop. + protected abstract void OnCompletedCore(); + + /// + /// Disposes the observer, causing it to transition to the stopped state. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Core implementation of . + /// + /// true if the Dispose call was triggered by the method; false if it was triggered by the finalizer. + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Volatile.Write(ref _isStopped, 1); + } + } + + internal bool Fail(Exception error) + { + if (Interlocked.Exchange(ref _isStopped, 1) == 0) + { + OnErrorCore(error); + return true; + } + + return false; + } + } +} diff --git a/LibExternal/System.Reactive/Runtime/CompilerServices/TaskObservableMethodBuilder.cs b/LibExternal/System.Reactive/Runtime/CompilerServices/TaskObservableMethodBuilder.cs new file mode 100644 index 0000000..92b6e32 --- /dev/null +++ b/LibExternal/System.Reactive/Runtime/CompilerServices/TaskObservableMethodBuilder.cs @@ -0,0 +1,363 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive; +using System.Reactive.Concurrency; +using System.Reactive.Disposables; +using System.Reactive.Subjects; +using System.Security; + +namespace System.Runtime.CompilerServices +{ + /// + /// Represents a builder for asynchronous methods that return a task-like . + /// + /// The type of the elements in the sequence. +#pragma warning disable CA1815 // (Override equals on value types.) Method only meant to be used by await/async generated code, so equality is not required. + public struct TaskObservableMethodBuilder +#pragma warning restore CA1815 + { + /// + /// The compiler-generated asynchronous state machine representing the execution flow of the asynchronous + /// method whose return type is a task-like . + /// + private IAsyncStateMachine _stateMachine; + + /// + /// The underlying observable sequence representing the result produced by the asynchronous method. + /// + private TaskObservable _inner; + + /// + /// Creates an instance of the struct. + /// + /// A new instance of the struct. +#pragma warning disable CA1000 // (Do not declare static members on generic types.) Async method builders are required to define a static Create method, and are require to be generic when the async type produces a result. + public static TaskObservableMethodBuilder Create() => default; +#pragma warning restore CA1000 // Do not declare static members on generic types + + /// + /// Begins running the builder with the associated state machine. + /// + /// The type of the state machine. + /// The state machine instance, passed by reference. + /// is null. +#pragma warning disable CA1045 // (Avoid ref.) Required because this is an async method builder + public void Start(ref TStateMachine stateMachine) +#pragma warning restore CA1045 + where TStateMachine : IAsyncStateMachine + { + if (stateMachine == null) + { + throw new ArgumentNullException(nameof(stateMachine)); + } + + stateMachine.MoveNext(); + } + + /// + /// Associates the builder with the specified state machine. + /// + /// The state machine instance to associate with the builder. + /// is null. + /// The state machine was previously set. + public void SetStateMachine(IAsyncStateMachine stateMachine) + { + if (_stateMachine != null) + { + throw new InvalidOperationException(); + } + + _stateMachine = stateMachine ?? throw new ArgumentNullException(nameof(stateMachine)); + } + + /// + /// Marks the observable as successfully completed. + /// + /// The result to use to complete the observable sequence. + /// The observable has already completed. + public void SetResult(T result) + { + if (_inner == null) + { + _inner = new TaskObservable(result); + } + else + { + _inner.SetResult(result); + } + } + + /// + /// Marks the observable as failed and binds the specified exception to the observable sequence. + /// + /// The exception to bind to the observable sequence. + /// is null. + /// The observable has already completed. + public void SetException(Exception exception) + { + if (exception == null) + { + throw new ArgumentNullException(nameof(exception)); + } + + if (_inner == null) + { + _inner = new TaskObservable(exception); + } + else + { + _inner.SetException(exception); + } + } + + /// + /// Gets the observable sequence for this builder. + /// + public ITaskObservable Task => _inner ??= new TaskObservable(); + + /// + /// Schedules the state machine to proceed to the next action when the specified awaiter completes. + /// + /// The type of the awaiter. + /// The type of the state machine. + /// The awaiter. + /// The state machine. +#pragma warning disable CA1045 // (Avoid ref.) Required because this is an async method builder + public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) +#pragma warning restore CA1045 + where TAwaiter : INotifyCompletion + where TStateMachine : IAsyncStateMachine + { + try + { + if (_stateMachine == null) + { + var ignored = Task; // NB: Ensure we have the observable backed by an async subject ready. + + _stateMachine = stateMachine; + _stateMachine.SetStateMachine(_stateMachine); + } + + // NB: Rx has historically not bothered with execution contexts, so we don't do it here either. + + awaiter.OnCompleted(_stateMachine.MoveNext); + } + catch (Exception ex) + { + // NB: Prevent reentrancy into the async state machine when an exception would be observed + // by the caller. This could cause concurrent execution of the async method. Instead, + // rethrow the exception elsewhere. + + Rethrow(ex); + } + } + + /// + /// Schedules the state machine to proceed to the next action when the specified awaiter completes. + /// + /// The type of the awaiter. + /// The type of the state machine. + /// The awaiter. + /// The state machine. + [SecuritySafeCritical] +#pragma warning disable CA1045 // (Avoid ref.) Required because this is an async method builder + public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) +#pragma warning restore CA1045 + where TAwaiter : ICriticalNotifyCompletion + where TStateMachine : IAsyncStateMachine + { + try + { + if (_stateMachine == null) + { + var ignored = Task; // NB: Ensure we have the observable backed by an async subject ready. + + _stateMachine = stateMachine; + _stateMachine.SetStateMachine(_stateMachine); + } + + // NB: Rx has historically not bothered with execution contexts, so we don't do it here either. + + awaiter.UnsafeOnCompleted(_stateMachine.MoveNext); + } + catch (Exception ex) + { + // NB: Prevent reentrancy into the async state machine when an exception would be observed + // by the caller. This could cause concurrent execution of the async method. Instead, + // rethrow the exception elsewhere. + + Rethrow(ex); + } + } + + /// + /// Rethrows an exception that was thrown from an awaiter's OnCompleted methods. + /// + /// The exception to rethrow. + private static void Rethrow(Exception exception) + { + Scheduler.Default.Schedule(exception, (ex, recurse) => ex.Throw()); + } + + /// + /// Implementation of the IObservable<T> interface compatible with async method return types. + /// + /// + /// This class implements a "task-like" type that can be used as the return type of an asynchronous + /// method in C# 7.0 and beyond. For example: + /// + /// async Observable<int> RxAsync() + /// { + /// var res = await Observable.Return(21).Delay(TimeSpan.FromSeconds(1)); + /// return res * 2; + /// } + /// + /// + internal sealed class TaskObservable : ITaskObservable, ITaskObservableAwaiter + { + /// + /// The underlying observable sequence to subscribe to in case the asynchronous method did not + /// finish synchronously. + /// + private readonly AsyncSubject? _subject; + + /// + /// The result returned by the asynchronous method in case the method finished synchronously. + /// + private readonly T? _result; + + /// + /// The exception thrown by the asynchronous method in case the method finished synchronously. + /// + private readonly Exception? _exception; + + /// + /// Creates a new for an asynchronous method that has not finished yet. + /// + public TaskObservable() + { + _subject = new AsyncSubject(); + } + + /// + /// Creates a new for an asynchronous method that synchronously returned + /// the specified value. + /// + /// The result returned by the asynchronous method. + public TaskObservable(T result) + { + _result = result; + } + + /// + /// Creates a new for an asynchronous method that synchronously threw + /// the specified . + /// + /// The exception thrown by the asynchronous method. + public TaskObservable(Exception exception) + { + _exception = exception; + } + + /// + /// Marks the observable as successfully completed. + /// + /// The result to use to complete the observable sequence. + /// The observable has already completed. + public void SetResult(T result) + { + if (IsCompleted) + { + throw new InvalidOperationException(); + } + + _subject!.OnNext(result); + _subject.OnCompleted(); + } + + /// + /// Marks the observable as failed and binds the specified exception to the observable sequence. + /// + /// The exception to bind to the observable sequence. + /// is null. + /// The observable has already completed. + public void SetException(Exception exception) + { + if (IsCompleted) + { + throw new InvalidOperationException(); + } + + _subject!.OnError(exception); + } + + /// + /// Subscribes the given observer to the observable sequence. + /// + /// Observer that will receive notifications from the observable sequence. + /// Disposable object representing an observer's subscription to the observable sequence. + /// is null. + public IDisposable Subscribe(IObserver observer) + { + if (_subject != null) + { + return _subject.Subscribe(observer); + } + + if (_exception != null) + { + observer.OnError(_exception); + return Disposable.Empty; + } + + observer.OnNext(_result!); + return Disposable.Empty; + } + + /// + /// Gets an awaiter that can be used to await the eventual completion of the observable sequence. + /// + /// An awaiter that can be used to await the eventual completion of the observable sequence. + public ITaskObservableAwaiter GetAwaiter() => this; + + /// + /// Gets a Boolean indicating whether the observable sequence has completed. + /// + public bool IsCompleted => _subject?.IsCompleted ?? true; + + /// + /// Gets the result produced by the observable sequence. + /// + /// The result produced by the observable sequence. + public T GetResult() + { + if (_subject != null) + { + return _subject.GetResult(); + } + + _exception?.Throw(); + + return _result!; + } + + /// + /// Attaches the specified to the observable sequence. + /// + /// The continuation to attach. + public void OnCompleted(Action continuation) + { + if (_subject != null) + { + _subject.OnCompleted(continuation); + } + else + { + continuation(); + } + } + } + } +} diff --git a/LibExternal/System.Reactive/Strings_Core.Designer.cs b/LibExternal/System.Reactive/Strings_Core.Designer.cs new file mode 100644 index 0000000..b619d16 --- /dev/null +++ b/LibExternal/System.Reactive/Strings_Core.Designer.cs @@ -0,0 +1,171 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace System.Reactive +{ + using System.Reflection; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Strings_Core + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Strings_Core() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if (object.ReferenceEquals(resourceMan, null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("System.Reactive.Strings_Core", typeof(Strings_Core).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Using the Scheduler.{0} property is no longer supported due to refactoring of the API surface and elimination of platform-specific dependencies. Please include System.Reactive.PlatformServices for your target platform and use the {0}Scheduler type instead. If you're building a Windows Store app, notice some schedulers are no longer supported. Consider using Scheduler.Default instead.. + /// + internal static string CANT_OBTAIN_SCHEDULER + { + get + { + return ResourceManager.GetString("CANT_OBTAIN_SCHEDULER", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to OnCompleted notification doesn't have a value.. + /// + internal static string COMPLETED_NO_VALUE + { + get + { + return ResourceManager.GetString("COMPLETED_NO_VALUE", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Disposable has already been assigned.. + /// + internal static string DISPOSABLE_ALREADY_ASSIGNED + { + get + { + return ResourceManager.GetString("DISPOSABLE_ALREADY_ASSIGNED", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Disposables collection can not contain null values.. + /// + internal static string DISPOSABLES_CANT_CONTAIN_NULL + { + get + { + return ResourceManager.GetString("DISPOSABLES_CANT_CONTAIN_NULL", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to start monitoring system clock changes.. + /// + internal static string FAILED_CLOCK_MONITORING + { + get + { + return ResourceManager.GetString("FAILED_CLOCK_MONITORING", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Heap is empty.. + /// + internal static string HEAP_EMPTY + { + get + { + return ResourceManager.GetString("HEAP_EMPTY", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Observer has already terminated.. + /// + internal static string OBSERVER_TERMINATED + { + get + { + return ResourceManager.GetString("OBSERVER_TERMINATED", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Reentrancy has been detected.. + /// + internal static string REENTRANCY_DETECTED + { + get + { + return ResourceManager.GetString("REENTRANCY_DETECTED", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This scheduler operation has already been awaited.. + /// + internal static string SCHEDULER_OPERATION_ALREADY_AWAITED + { + get + { + return ResourceManager.GetString("SCHEDULER_OPERATION_ALREADY_AWAITED", resourceCulture); + } + } + } +} diff --git a/LibExternal/System.Reactive/Strings_Core.resx b/LibExternal/System.Reactive/Strings_Core.resx new file mode 100644 index 0000000..e6f35a7 --- /dev/null +++ b/LibExternal/System.Reactive/Strings_Core.resx @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Using the Scheduler.{0} property is no longer supported due to refactoring of the API surface and elimination of platform-specific dependencies. Please include System.Reactive.PlatformServices for your target platform and use the {0}Scheduler type instead. If you're building a Windows Store app, notice some schedulers are no longer supported. Consider using Scheduler.Default instead. + + + OnCompleted notification doesn't have a value. + + + Disposables collection can not contain null values. + + + Disposable has already been assigned. + + + Failed to start monitoring system clock changes. + + + Heap is empty. + + + Observer has already terminated. + + + Reentrancy has been detected. + + + This scheduler operation has already been awaited. + Only on .NET 4.5 and above. + + \ No newline at end of file diff --git a/LibExternal/System.Reactive/Strings_Linq.Designer.cs b/LibExternal/System.Reactive/Strings_Linq.Designer.cs new file mode 100644 index 0000000..8bd0869 --- /dev/null +++ b/LibExternal/System.Reactive/Strings_Linq.Designer.cs @@ -0,0 +1,248 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace System.Reactive +{ + using System.Reflection; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Strings_Linq + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Strings_Linq() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if (object.ReferenceEquals(resourceMan, null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("System.Reactive.Strings_Linq", typeof(Strings_Linq).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to {0} cannot be called when the scheduler is already running. Try using Sleep instead.. + /// + internal static string CANT_ADVANCE_WHILE_RUNNING + { + get + { + return ResourceManager.GetString("CANT_ADVANCE_WHILE_RUNNING", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find event '{0}' on object of type '{1}'.. + /// + internal static string COULD_NOT_FIND_INSTANCE_EVENT + { + get + { + return ResourceManager.GetString("COULD_NOT_FIND_INSTANCE_EVENT", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find event '{0}' on type '{1}'.. + /// + internal static string COULD_NOT_FIND_STATIC_EVENT + { + get + { + return ResourceManager.GetString("COULD_NOT_FIND_STATIC_EVENT", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Add method should take 1 parameter.. + /// + internal static string EVENT_ADD_METHOD_SHOULD_TAKE_ONE_PARAMETER + { + get + { + return ResourceManager.GetString("EVENT_ADD_METHOD_SHOULD_TAKE_ONE_PARAMETER", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The second parameter of the event delegate must be assignable to '{0}'.. + /// + internal static string EVENT_ARGS_NOT_ASSIGNABLE + { + get + { + return ResourceManager.GetString("EVENT_ARGS_NOT_ASSIGNABLE", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Event is missing the add method.. + /// + internal static string EVENT_MISSING_ADD_METHOD + { + get + { + return ResourceManager.GetString("EVENT_MISSING_ADD_METHOD", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Event is missing the remove method.. + /// + internal static string EVENT_MISSING_REMOVE_METHOD + { + get + { + return ResourceManager.GetString("EVENT_MISSING_REMOVE_METHOD", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The event delegate must have a void return type.. + /// + internal static string EVENT_MUST_RETURN_VOID + { + get + { + return ResourceManager.GetString("EVENT_MUST_RETURN_VOID", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The event delegate must have exactly two parameters.. + /// + internal static string EVENT_PATTERN_REQUIRES_TWO_PARAMETERS + { + get + { + return ResourceManager.GetString("EVENT_PATTERN_REQUIRES_TWO_PARAMETERS", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Remove method should take 1 parameter.. + /// + internal static string EVENT_REMOVE_METHOD_SHOULD_TAKE_ONE_PARAMETER + { + get + { + return ResourceManager.GetString("EVENT_REMOVE_METHOD_SHOULD_TAKE_ONE_PARAMETER", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The first parameter of the event delegate must be assignable to '{0}'.. + /// + internal static string EVENT_SENDER_NOT_ASSIGNABLE + { + get + { + return ResourceManager.GetString("EVENT_SENDER_NOT_ASSIGNABLE", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Remove method of a WinRT event should take an EventRegistrationToken.. + /// + internal static string EVENT_WINRT_REMOVE_METHOD_SHOULD_TAKE_ERT + { + get + { + return ResourceManager.GetString("EVENT_WINRT_REMOVE_METHOD_SHOULD_TAKE_ERT", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sequence contains more than one element.. + /// + internal static string MORE_THAN_ONE_ELEMENT + { + get + { + return ResourceManager.GetString("MORE_THAN_ONE_ELEMENT", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sequence contains more than one matching element.. + /// + internal static string MORE_THAN_ONE_MATCHING_ELEMENT + { + get + { + return ResourceManager.GetString("MORE_THAN_ONE_MATCHING_ELEMENT", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sequence contains no elements.. + /// + internal static string NO_ELEMENTS + { + get + { + return ResourceManager.GetString("NO_ELEMENTS", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sequence contains no matching element.. + /// + internal static string NO_MATCHING_ELEMENTS + { + get + { + return ResourceManager.GetString("NO_MATCHING_ELEMENTS", resourceCulture); + } + } + } +} diff --git a/LibExternal/System.Reactive/Strings_Linq.resx b/LibExternal/System.Reactive/Strings_Linq.resx new file mode 100644 index 0000000..56b7468 --- /dev/null +++ b/LibExternal/System.Reactive/Strings_Linq.resx @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + {0} cannot be called when the scheduler is already running. Try using Sleep instead. + + + Could not find event '{0}' on object of type '{1}'. + + + Could not find event '{0}' on type '{1}'. + + + Add method should take 1 parameter. + + + The second parameter of the event delegate must be assignable to '{0}'. + + + Event is missing the add method. + + + Event is missing the remove method. + + + The event delegate must have a void return type. + + + The event delegate must have exactly two parameters. + + + Remove method should take 1 parameter. + + + The first parameter of the event delegate must be assignable to '{0}'. + + + Remove method of a WinRT event should take an EventRegistrationToken. + Only on platforms supporting WinRT. + + + Sequence contains more than one element. + + + Sequence contains more than one matching element. + + + Sequence contains no elements. + + + Sequence contains no matching element. + + \ No newline at end of file diff --git a/LibExternal/System.Reactive/Strings_PlatformServices.Designer.cs b/LibExternal/System.Reactive/Strings_PlatformServices.Designer.cs new file mode 100644 index 0000000..a8f6c5a --- /dev/null +++ b/LibExternal/System.Reactive/Strings_PlatformServices.Designer.cs @@ -0,0 +1,83 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace System.Reactive +{ + using System.Reflection; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Strings_PlatformServices + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Strings_PlatformServices() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if (object.ReferenceEquals(resourceMan, null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("System.Reactive.Strings_PlatformServices", typeof(Strings_PlatformServices).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The WinRT thread pool doesn't support creating periodic timers with a period below 1 millisecond.. + /// + internal static string WINRT_NO_SUB1MS_TIMERS + { + get + { + return ResourceManager.GetString("WINRT_NO_SUB1MS_TIMERS", resourceCulture); + } + } + } +} diff --git a/LibExternal/System.Reactive/Strings_PlatformServices.resx b/LibExternal/System.Reactive/Strings_PlatformServices.resx new file mode 100644 index 0000000..f796d0a --- /dev/null +++ b/LibExternal/System.Reactive/Strings_PlatformServices.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The WinRT thread pool doesn't support creating periodic timers with a period below 1 millisecond. + + \ No newline at end of file diff --git a/LibExternal/System.Reactive/Strings_Providers.Designer.cs b/LibExternal/System.Reactive/Strings_Providers.Designer.cs new file mode 100644 index 0000000..b6634f1 --- /dev/null +++ b/LibExternal/System.Reactive/Strings_Providers.Designer.cs @@ -0,0 +1,105 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace System.Reactive +{ + using System.Reflection; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Strings_Providers + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Strings_Providers() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if (object.ReferenceEquals(resourceMan, null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("System.Reactive.Strings_Providers", typeof(Strings_Providers).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Expected Qbservable.ToQueryable.. + /// + internal static string EXPECTED_TOQUERYABLE_METHODCALL + { + get + { + return ResourceManager.GetString("EXPECTED_TOQUERYABLE_METHODCALL", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid expression tree type.. + /// + internal static string INVALID_TREE_TYPE + { + get + { + return ResourceManager.GetString("INVALID_TREE_TYPE", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to There is no method '{0}' on type '{1}' that matches the specified arguments.. + /// + internal static string NO_MATCHING_METHOD_FOUND + { + get + { + return ResourceManager.GetString("NO_MATCHING_METHOD_FOUND", resourceCulture); + } + } + } +} diff --git a/LibExternal/System.Reactive/Strings_Providers.resx b/LibExternal/System.Reactive/Strings_Providers.resx new file mode 100644 index 0000000..aba90dd --- /dev/null +++ b/LibExternal/System.Reactive/Strings_Providers.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Expected Qbservable.ToQueryable. + + + Invalid expression tree type. + + + There is no method '{0}' on type '{1}' that matches the specified arguments. + + \ No newline at end of file diff --git a/LibExternal/System.Reactive/Subjects/AsyncSubject.cs b/LibExternal/System.Reactive/Subjects/AsyncSubject.cs new file mode 100644 index 0000000..5ac108e --- /dev/null +++ b/LibExternal/System.Reactive/Subjects/AsyncSubject.cs @@ -0,0 +1,459 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace System.Reactive.Subjects +{ + /// + /// Represents the result of an asynchronous operation. + /// The last value before the OnCompleted notification, or the error received through OnError, is sent to all subscribed observers. + /// + /// The type of the elements processed by the subject. + public sealed class AsyncSubject : SubjectBase, INotifyCompletion + { + #region Fields + + private AsyncSubjectDisposable[] _observers; + private T? _value; + private bool _hasValue; + private Exception? _exception; + +#pragma warning disable CA1825 // (Avoid zero-length array allocations.) The identity of these arrays matters, so we can't use the shared Array.Empty() instance + /// + /// A pre-allocated empty array indicating the AsyncSubject has terminated. + /// + private static readonly AsyncSubjectDisposable[] Terminated = new AsyncSubjectDisposable[0]; + /// + /// A pre-allocated empty array indicating the AsyncSubject has been disposed. + /// + private static readonly AsyncSubjectDisposable[] Disposed = new AsyncSubjectDisposable[0]; +#pragma warning restore CA1825 + + #endregion + + #region Constructors + + /// + /// Creates a subject that can only receive one value and that value is cached for all future observations. + /// + public AsyncSubject() => _observers = Array.Empty(); + + #endregion + + #region Properties + + /// + /// Indicates whether the subject has observers subscribed to it. + /// + public override bool HasObservers => Volatile.Read(ref _observers).Length != 0; + + /// + /// Indicates whether the subject has been disposed. + /// + public override bool IsDisposed => Volatile.Read(ref _observers) == Disposed; + + #endregion + + #region Methods + + #region IObserver implementation + + /// + /// Notifies all subscribed observers about the end of the sequence, also causing the last received value to be sent out (if any). + /// + public override void OnCompleted() + { + for (; ; ) + { + var observers = Volatile.Read(ref _observers); + + if (observers == Disposed) + { + _exception = null; + ThrowDisposed(); + break; + } + + if (observers == Terminated) + { + break; + } + + if (Interlocked.CompareExchange(ref _observers, Terminated, observers) == observers) + { + var hasValue = _hasValue; + + if (hasValue) + { + var value = _value; + + foreach (var observer in observers) + { + var o = observer.Observer; + + if (o != null) + { + o.OnNext(value!); + o.OnCompleted(); + } + } + } + else + { + foreach (var observer in observers) + { + observer.Observer?.OnCompleted(); + } + } + } + } + } + + /// + /// Notifies all subscribed observers about the exception. + /// + /// The exception to send to all observers. + /// is null. + public override void OnError(Exception error) + { + if (error == null) + { + throw new ArgumentNullException(nameof(error)); + } + + for (; ; ) + { + var observers = Volatile.Read(ref _observers); + + if (observers == Disposed) + { + _exception = null; + _value = default; + ThrowDisposed(); + break; + } + + if (observers == Terminated) + { + break; + } + + _exception = error; + + if (Interlocked.CompareExchange(ref _observers, Terminated, observers) == observers) + { + foreach (var observer in observers) + { + observer.Observer?.OnError(error); + } + } + } + + } + + /// + /// Sends a value to the subject. The last value received before successful termination will be sent to all subscribed and future observers. + /// + /// The value to store in the subject. + public override void OnNext(T value) + { + var observers = Volatile.Read(ref _observers); + + if (observers == Disposed) + { + _value = default; + _exception = null; + ThrowDisposed(); + return; + } + + if (observers == Terminated) + { + return; + } + + _value = value; + _hasValue = true; + } + + #endregion + + #region IObservable implementation + + /// + /// Subscribes an observer to the subject. + /// + /// Observer to subscribe to the subject. + /// Disposable object that can be used to unsubscribe the observer from the subject. + /// is null. + public override IDisposable Subscribe(IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + var disposable = default(AsyncSubjectDisposable); + for (; ; ) + { + var observers = Volatile.Read(ref _observers); + + if (observers == Disposed) + { + _value = default; + _exception = null; + ThrowDisposed(); + + break; + } + + if (observers == Terminated) + { + var ex = _exception; + + if (ex != null) + { + observer.OnError(ex); + } + else + { + if (_hasValue) + { + observer.OnNext(_value!); + } + + observer.OnCompleted(); + } + + break; + } + + disposable ??= new AsyncSubjectDisposable(this, observer); + + var n = observers.Length; + var b = new AsyncSubjectDisposable[n + 1]; + + Array.Copy(observers, 0, b, 0, n); + + b[n] = disposable; + + if (Interlocked.CompareExchange(ref _observers, b, observers) == observers) + { + return disposable; + } + } + + return Disposable.Empty; + } + + private void Unsubscribe(AsyncSubjectDisposable observer) + { + for (; ; ) + { + var a = Volatile.Read(ref _observers); + var n = a.Length; + + if (n == 0) + { + break; + } + + var j = Array.IndexOf(a, observer); + + if (j < 0) + { + break; + } + + AsyncSubjectDisposable[] b; + + if (n == 1) + { + b = Array.Empty(); + } + else + { + b = new AsyncSubjectDisposable[n - 1]; + + Array.Copy(a, 0, b, 0, j); + Array.Copy(a, j + 1, b, j, n - j - 1); + } + + if (Interlocked.CompareExchange(ref _observers, b, a) == a) + { + break; + } + } + } + + /// + /// A disposable connecting the AsyncSubject and an IObserver. + /// + private sealed class AsyncSubjectDisposable : IDisposable + { + private AsyncSubject _subject; + private volatile IObserver? _observer; + + public AsyncSubjectDisposable(AsyncSubject subject, IObserver observer) + { + _subject = subject; + _observer = observer; + } + + public IObserver? Observer => _observer; + + public void Dispose() + { + var observer = Interlocked.Exchange(ref _observer, null); + if (observer == null) + { + return; + } + + _subject.Unsubscribe(this); + _subject = null!; + } + } + + #endregion + + #region IDisposable implementation + + private static void ThrowDisposed() => throw new ObjectDisposedException(string.Empty); + + /// + /// Unsubscribe all observers and release resources. + /// + public override void Dispose() + { + if (Interlocked.Exchange(ref _observers, Disposed) != Disposed) + { + _exception = null; + _value = default; + _hasValue = false; + } + } + + #endregion + + #region Await support + + /// + /// Gets an awaitable object for the current AsyncSubject. + /// + /// Object that can be awaited. + public AsyncSubject GetAwaiter() => this; + + /// + /// Specifies a callback action that will be invoked when the subject completes. + /// + /// Callback action that will be invoked when the subject completes. + /// is null. + public void OnCompleted(Action continuation) + { + if (continuation == null) + { + throw new ArgumentNullException(nameof(continuation)); + } + + // + // [OK] Use of unsafe Subscribe: this type's Subscribe implementation is safe. + // + Subscribe/*Unsafe*/(new AwaitObserver(continuation)); + } + + private sealed class AwaitObserver : IObserver + { + private readonly SynchronizationContext? _context; + private readonly Action _callback; + + public AwaitObserver(Action callback) + { + _context = SynchronizationContext.Current; + _callback = callback; + } + + public void OnCompleted() => InvokeOnOriginalContext(); + + public void OnError(Exception error) => InvokeOnOriginalContext(); + + public void OnNext(T value) { } + + private void InvokeOnOriginalContext() + { + if (_context != null) + { + // + // No need for OperationStarted and OperationCompleted calls here; + // this code is invoked through await support and will have a way + // to observe its start/complete behavior, either through returned + // Task objects or the async method builder's interaction with the + // SynchronizationContext object. + // + _context.Post(static c => ((Action)c!)(), _callback); + } + else + { + _callback(); + } + } + } + + /// + /// Gets whether the AsyncSubject has completed. + /// + public bool IsCompleted => Volatile.Read(ref _observers) == Terminated; + + /// + /// Gets the last element of the subject, potentially blocking until the subject completes successfully or exceptionally. + /// + /// The last element of the subject. Throws an InvalidOperationException if no element was received. + /// The source sequence is empty. + public T GetResult() + { + if (Volatile.Read(ref _observers) != Terminated) + { + using var e = new ManualResetEventSlim(initialState: false); + + // + // [OK] Use of unsafe Subscribe: this type's Subscribe implementation is safe. + // + Subscribe/*Unsafe*/(new BlockingObserver(e)); + + e.Wait(); + } + + _exception?.Throw(); + + if (!_hasValue) + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + + return _value!; + } + + private sealed class BlockingObserver : IObserver + { + private readonly ManualResetEventSlim _e; + + public BlockingObserver(ManualResetEventSlim e) => _e = e; + + public void OnCompleted() => Done(); + + public void OnError(Exception error) => Done(); + + public void OnNext(T value) { } + + private void Done() => _e.Set(); + } + + #endregion + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Subjects/BehaviorSubject.cs b/LibExternal/System.Reactive/Subjects/BehaviorSubject.cs new file mode 100644 index 0000000..5647a9f --- /dev/null +++ b/LibExternal/System.Reactive/Subjects/BehaviorSubject.cs @@ -0,0 +1,332 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics.CodeAnalysis; +using System.Reactive.Disposables; +using System.Threading; + +namespace System.Reactive.Subjects +{ + /// + /// Represents a value that changes over time. + /// Observers can subscribe to the subject to receive the last (or initial) value and all subsequent notifications. + /// + /// The type of the elements processed by the subject. + public sealed class BehaviorSubject : SubjectBase + { + #region Fields + + private readonly object _gate = new(); + + private ImmutableList> _observers; + private bool _isStopped; + private T _value; + private Exception? _exception; + private bool _isDisposed; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class which creates a subject that caches its last value and starts with the specified value. + /// + /// Initial value sent to observers when no other value has been received by the subject yet. + public BehaviorSubject(T value) + { + _value = value; + _observers = ImmutableList>.Empty; + } + + #endregion + + #region Properties + + /// + /// Indicates whether the subject has observers subscribed to it. + /// + public override bool HasObservers => _observers?.Data.Length > 0; + + /// + /// Indicates whether the subject has been disposed. + /// + public override bool IsDisposed + { + get + { + lock (_gate) + { + return _isDisposed; + } + } + } + + /// + /// Gets the current value or throws an exception. + /// + /// The initial value passed to the constructor until is called; after which, the last value passed to . + /// + /// is frozen after is called. + /// After is called, always throws the specified exception. + /// An exception is always thrown after is called. + /// + /// Reading is a thread-safe operation, though there's a potential race condition when or are being invoked concurrently. + /// In some cases, it may be necessary for a caller to use external synchronization to avoid race conditions. + /// + /// + /// Dispose was called. + public T Value + { + get + { + lock (_gate) + { + CheckDisposed(); + + _exception?.Throw(); + + return _value; + } + } + } + + #endregion + + #region Methods + + /// + /// Tries to get the current value or throws an exception. + /// + /// The initial value passed to the constructor until is called; after which, the last value passed to . + /// true if a value is available; false if the subject was disposed. + /// + /// The value returned from is frozen after is called. + /// After is called, always throws the specified exception. + /// + /// Calling is a thread-safe operation, though there's a potential race condition when or are being invoked concurrently. + /// In some cases, it may be necessary for a caller to use external synchronization to avoid race conditions. + /// + /// + public bool TryGetValue([MaybeNullWhen(false)] out T value) + { + lock (_gate) + { + if (_isDisposed) + { + value = default; + return false; + } + + _exception?.Throw(); + + value = _value; + return true; + } + } + + #region IObserver implementation + + /// + /// Notifies all subscribed observers about the end of the sequence. + /// + public override void OnCompleted() + { + IObserver[]? os = null; + + lock (_gate) + { + CheckDisposed(); + + if (!_isStopped) + { + os = _observers.Data; + _observers = ImmutableList>.Empty; + _isStopped = true; + } + } + + if (os != null) + { + foreach (var o in os) + { + o.OnCompleted(); + } + } + } + + /// + /// Notifies all subscribed observers about the exception. + /// + /// The exception to send to all observers. + /// is null. + public override void OnError(Exception error) + { + if (error == null) + { + throw new ArgumentNullException(nameof(error)); + } + + IObserver[]? os = null; + + lock (_gate) + { + CheckDisposed(); + + if (!_isStopped) + { + os = _observers.Data; + _observers = ImmutableList>.Empty; + _isStopped = true; + _exception = error; + } + } + + if (os != null) + { + foreach (var o in os) + { + o.OnError(error); + } + } + } + + /// + /// Notifies all subscribed observers about the arrival of the specified element in the sequence. + /// + /// The value to send to all observers. + public override void OnNext(T value) + { + IObserver[]? os = null; + + lock (_gate) + { + CheckDisposed(); + + if (!_isStopped) + { + _value = value; + os = _observers.Data; + } + } + + if (os != null) + { + foreach (var o in os) + { + o.OnNext(value); + } + } + } + + #endregion + + #region IObservable implementation + + /// + /// Subscribes an observer to the subject. + /// + /// Observer to subscribe to the subject. + /// Disposable object that can be used to unsubscribe the observer from the subject. + /// is null. + public override IDisposable Subscribe(IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + Exception? ex; + + lock (_gate) + { + CheckDisposed(); + + if (!_isStopped) + { + _observers = _observers.Add(observer); + observer.OnNext(_value); + return new Subscription(this, observer); + } + + ex = _exception; + } + + if (ex != null) + { + observer.OnError(ex); + } + else + { + observer.OnCompleted(); + } + + return Disposable.Empty; + } + + private void Unsubscribe(IObserver observer) + { + lock (_gate) + { + if (!_isDisposed) + { + _observers = _observers.Remove(observer); + } + } + } + + #endregion + + #region IDisposable implementation + + /// + /// Unsubscribe all observers and release resources. + /// + public override void Dispose() + { + lock (_gate) + { + _isDisposed = true; + _observers = null!; // NB: Disposed checks happen prior to accessing _observers. + _value = default!; + _exception = null; + } + } + + private void CheckDisposed() + { + if (_isDisposed) + { + throw new ObjectDisposedException(string.Empty); + } + } + + #endregion + + private sealed class Subscription : IDisposable + { + private BehaviorSubject _subject; + private IObserver? _observer; + + public Subscription(BehaviorSubject subject, IObserver observer) + { + _subject = subject; + _observer = observer; + } + + public void Dispose() + { + var observer = Interlocked.Exchange(ref _observer, null); + if (observer == null) + { + return; + } + + _subject.Unsubscribe(observer); + _subject = null!; + } + } + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Subjects/ConnectableObservable.cs b/LibExternal/System.Reactive/Subjects/ConnectableObservable.cs new file mode 100644 index 0000000..4fcf3c6 --- /dev/null +++ b/LibExternal/System.Reactive/Subjects/ConnectableObservable.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Linq; + +namespace System.Reactive.Subjects +{ + /// + /// Represents an observable wrapper that can be connected and disconnected from its underlying observable sequence. + /// + /// The type of the elements in the source sequence. + /// The type of the elements in the resulting sequence, after transformation through the subject. + internal sealed class ConnectableObservable : IConnectableObservable + { + private readonly ISubject _subject; + private readonly IObservable _source; + private readonly object _gate; + + private Connection? _connection; + + /// + /// Creates an observable that can be connected and disconnected from its source. + /// + /// Underlying observable source sequence that can be connected and disconnected from the wrapper. + /// Subject exposed by the connectable observable, receiving data from the underlying source sequence upon connection. + public ConnectableObservable(IObservable source, ISubject subject) + { + _subject = subject; + _source = source.AsObservable(); // This gets us auto-detach behavior; otherwise, we'd have to roll our own, including trampoline installation. + _gate = new object(); + } + + /// + /// Connects the observable wrapper to its source. All subscribed observers will receive values from the underlying observable sequence as long as the connection is established. + /// + /// Disposable object used to disconnect the observable wrapper from its source, causing subscribed observer to stop receiving values from the underlying observable sequence. + public IDisposable Connect() + { + lock (_gate) + { + if (_connection == null) + { + var subscription = _source.SubscribeSafe(_subject); + _connection = new Connection(this, subscription); + } + + return _connection; + } + } + + private sealed class Connection : IDisposable + { + private readonly ConnectableObservable _parent; + private IDisposable? _subscription; + + public Connection(ConnectableObservable parent, IDisposable subscription) + { + _parent = parent; + _subscription = subscription; + } + + public void Dispose() + { + lock (_parent._gate) + { + if (_subscription != null) + { + _subscription.Dispose(); + _subscription = null; + + _parent._connection = null; + } + } + } + } + + /// + /// Subscribes an observer to the observable sequence. No values from the underlying observable source will be received unless a connection was established through the Connect method. + /// + /// Observer that will receive values from the underlying observable source when the current ConnectableObservable instance is connected through a call to Connect. + /// Disposable used to unsubscribe from the observable sequence. + public IDisposable Subscribe(IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + return _subject.SubscribeSafe(observer); + } + } +} diff --git a/LibExternal/System.Reactive/Subjects/IConnectableObservable.cs b/LibExternal/System.Reactive/Subjects/IConnectableObservable.cs new file mode 100644 index 0000000..95ff766 --- /dev/null +++ b/LibExternal/System.Reactive/Subjects/IConnectableObservable.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Subjects +{ + /// + /// Represents an observable wrapper that can be connected and disconnected from its underlying observable sequence. + /// + /// + /// The type of the elements in the sequence. + /// This type parameter is covariant. That is, you can use either the type you specified or any type that is more derived. For more information about covariance and contravariance, see Covariance and Contravariance in Generics. + /// + public interface IConnectableObservable : IObservable + { + /// + /// Connects the observable wrapper to its source. All subscribed observers will receive values from the underlying observable sequence as long as the connection is established. + /// + /// Disposable used to disconnect the observable wrapper from its source, causing subscribed observer to stop receiving values from the underlying observable sequence. + IDisposable Connect(); + } +} diff --git a/LibExternal/System.Reactive/Subjects/ISubject.Multi.cs b/LibExternal/System.Reactive/Subjects/ISubject.Multi.cs new file mode 100644 index 0000000..fc3f677 --- /dev/null +++ b/LibExternal/System.Reactive/Subjects/ISubject.Multi.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Subjects +{ + /// + /// Represents an object that is both an observable sequence as well as an observer. + /// + /// + /// The type of the elements received by the subject. + /// This type parameter is contravariant. That is, you can use either the type you specified or any type that is less derived. For more information about covariance and contravariance, see Covariance and Contravariance in Generics. + /// + /// + /// The type of the elements produced by the subject. + /// This type parameter is covariant. That is, you can use either the type you specified or any type that is more derived. For more information about covariance and contravariance, see Covariance and Contravariance in Generics. + /// + public interface ISubject : IObserver, IObservable + { + } +} diff --git a/LibExternal/System.Reactive/Subjects/ISubject.cs b/LibExternal/System.Reactive/Subjects/ISubject.cs new file mode 100644 index 0000000..f6880ed --- /dev/null +++ b/LibExternal/System.Reactive/Subjects/ISubject.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Subjects +{ + /// + /// Represents an object that is both an observable sequence as well as an observer. + /// + /// The type of the elements processed by the subject. + public interface ISubject : ISubject + { + } +} diff --git a/LibExternal/System.Reactive/Subjects/ReplaySubject.cs b/LibExternal/System.Reactive/Subjects/ReplaySubject.cs new file mode 100644 index 0000000..aedb1a6 --- /dev/null +++ b/LibExternal/System.Reactive/Subjects/ReplaySubject.cs @@ -0,0 +1,960 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reactive.Concurrency; +using System.Reactive.Disposables; +using System.Threading; + +namespace System.Reactive.Subjects +{ + /// + /// Represents an object that is both an observable sequence as well as an observer. + /// Each notification is broadcasted to all subscribed and future observers, subject to buffer trimming policies. + /// + /// The type of the elements processed by the subject. + public sealed class ReplaySubject : SubjectBase + { + #region Fields + + /// + /// Underlying optimized implementation of the replay subject. + /// + private readonly SubjectBase _implementation; + + #endregion + + #region Constructors + + #region All + + /// + /// Initializes a new instance of the class. + /// + public ReplaySubject() + : this(int.MaxValue) + { + } + + /// + /// Initializes a new instance of the class with the specified scheduler. + /// + /// Scheduler the observers are invoked on. + /// is null. + public ReplaySubject(IScheduler scheduler) + { + _implementation = new ReplayByTime(scheduler); + } + + #endregion + + #region Count + + /// + /// Initializes a new instance of the class with the specified buffer size. + /// + /// Maximum element count of the replay buffer. + /// is less than zero. + public ReplaySubject(int bufferSize) + { + _implementation = bufferSize switch + { + 1 => new ReplayOne(), + int.MaxValue => new ReplayAll(), + _ => new ReplayMany(bufferSize), + }; + } + + /// + /// Initializes a new instance of the class with the specified buffer size and scheduler. + /// + /// Maximum element count of the replay buffer. + /// Scheduler the observers are invoked on. + /// is null. + /// is less than zero. + public ReplaySubject(int bufferSize, IScheduler scheduler) + { + _implementation = new ReplayByTime(bufferSize, scheduler); + } + + #endregion + + #region Time + + /// + /// Initializes a new instance of the class with the specified window. + /// + /// Maximum time length of the replay buffer. + /// is less than . + public ReplaySubject(TimeSpan window) + { + _implementation = new ReplayByTime(window); + } + + /// + /// Initializes a new instance of the class with the specified window and scheduler. + /// + /// Maximum time length of the replay buffer. + /// Scheduler the observers are invoked on. + /// is null. + /// is less than . + public ReplaySubject(TimeSpan window, IScheduler scheduler) + { + _implementation = new ReplayByTime(window, scheduler); + } + + #endregion + + #region Count & Time + + /// + /// Initializes a new instance of the class with the specified buffer size and window. + /// + /// Maximum element count of the replay buffer. + /// Maximum time length of the replay buffer. + /// is less than zero. -or- is less than . + public ReplaySubject(int bufferSize, TimeSpan window) + { + _implementation = new ReplayByTime(bufferSize, window); + } + + /// + /// Initializes a new instance of the class with the specified buffer size, window and scheduler. + /// + /// Maximum element count of the replay buffer. + /// Maximum time length of the replay buffer. + /// Scheduler the observers are invoked on. + /// is less than zero. -or- is less than . + /// is null. + public ReplaySubject(int bufferSize, TimeSpan window, IScheduler scheduler) + { + _implementation = new ReplayByTime(bufferSize, window, scheduler); + } + + #endregion + + #endregion + + #region Properties + + /// + /// Indicates whether the subject has observers subscribed to it. + /// + public override bool HasObservers => _implementation.HasObservers; + + /// + /// Indicates whether the subject has been disposed. + /// + public override bool IsDisposed => _implementation.IsDisposed; + + #endregion + + #region Methods + + #region IObserver implementation + + /// + /// Notifies all subscribed and future observers about the arrival of the specified element in the sequence. + /// + /// The value to send to all observers. + public override void OnNext(T value) => _implementation.OnNext(value); + + /// + /// Notifies all subscribed and future observers about the specified exception. + /// + /// The exception to send to all observers. + /// is null. + public override void OnError(Exception error) + { + if (error == null) + { + throw new ArgumentNullException(nameof(error)); + } + + _implementation.OnError(error); + } + + /// + /// Notifies all subscribed and future observers about the end of the sequence. + /// + public override void OnCompleted() => _implementation.OnCompleted(); + + #endregion + + #region IObservable implementation + + /// + /// Subscribes an observer to the subject. + /// + /// Observer to subscribe to the subject. + /// Disposable object that can be used to unsubscribe the observer from the subject. + /// is null. + public override IDisposable Subscribe(IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + return _implementation.Subscribe(observer); + } + + #endregion + + #region IDisposable implementation + + /// + /// Releases all resources used by the current instance of the class and unsubscribe all observers. + /// + public override void Dispose() => _implementation.Dispose(); + + #endregion + + #endregion + + private abstract class ReplayBase : SubjectBase + { + private readonly object _gate = new(); + + private ImmutableList> _observers; + + private bool _isStopped; + private Exception? _error; + private bool _isDisposed; + + protected ReplayBase() + { + _observers = ImmutableList>.Empty; + + _isStopped = false; + _error = null; + } + + public override bool HasObservers => _observers?.Data.Length > 0; + + public override bool IsDisposed + { + get + { + lock (_gate) + { + return _isDisposed; + } + } + } + + public override void OnNext(T value) + { + IScheduledObserver[]? o = null; + + lock (_gate) + { + CheckDisposed(); + + if (!_isStopped) + { + Next(value); + Trim(); + + o = _observers.Data; + foreach (var observer in o) + { + observer.OnNext(value); + } + } + } + + if (o != null) + { + foreach (var observer in o) + { + observer.EnsureActive(); + } + } + } + + public override void OnError(Exception error) + { + IScheduledObserver[]? o = null; + + lock (_gate) + { + CheckDisposed(); + + if (!_isStopped) + { + _isStopped = true; + _error = error; + Trim(); + + o = _observers.Data; + foreach (var observer in o) + { + observer.OnError(error); + } + + _observers = ImmutableList>.Empty; + } + } + + if (o != null) + { + foreach (var observer in o) + { + observer.EnsureActive(); + } + } + } + + public override void OnCompleted() + { + IScheduledObserver[]? o = null; + + lock (_gate) + { + CheckDisposed(); + + if (!_isStopped) + { + _isStopped = true; + Trim(); + + o = _observers.Data; + foreach (var observer in o) + { + observer.OnCompleted(); + } + + _observers = ImmutableList>.Empty; + } + } + + if (o != null) + { + foreach (var observer in o) + { + observer.EnsureActive(); + } + } + } + + public override IDisposable Subscribe(IObserver observer) + { + var so = CreateScheduledObserver(observer); + + var n = 0; + + var subscription = Disposable.Empty; + + lock (_gate) + { + CheckDisposed(); + + // + // Notice the v1.x behavior of always calling Trim is preserved here. + // + // This may be subject (pun intended) of debate: should this policy + // only be applied while the sequence is active? With the current + // behavior, a sequence will "die out" after it has terminated by + // continuing to drop OnNext notifications from the queue. + // + // In v1.x, this behavior was due to trimming based on the clock value + // returned by scheduler.Now, applied to all but the terminal message + // in the queue. Using the IStopwatch has the same effect. Either way, + // we guarantee the final notification will be observed, but there's + // no way to retain the buffer directly. One approach is to use the + // time-based TakeLast operator and apply an unbounded ReplaySubject + // to it. + // + // To conclude, we're keeping the behavior as-is for compatibility + // reasons with v1.x. + // + Trim(); + + n = Replay(so); + + if (_error != null) + { + n++; + so.OnError(_error); + } + else if (_isStopped) + { + n++; + so.OnCompleted(); + } + + if (!_isStopped) + { + subscription = new Subscription(this, so); + _observers = _observers.Add(so); + } + } + + so.EnsureActive(n); + + return subscription; + } + + public override void Dispose() + { + lock (_gate) + { + _isDisposed = true; + _observers = null!; // NB: Disposed checks happen prior to accessing _observers. + DisposeCore(); + } + } + + protected abstract void DisposeCore(); + + protected abstract void Next(T value); + + protected abstract int Replay(IObserver observer); + + protected abstract void Trim(); + + protected abstract IScheduledObserver CreateScheduledObserver(IObserver observer); + + private void CheckDisposed() + { + if (_isDisposed) + { + throw new ObjectDisposedException(string.Empty); + } + } + + private void Unsubscribe(IScheduledObserver observer) + { + lock (_gate) + { + if (!_isDisposed) + { + _observers = _observers.Remove(observer); + } + } + } + + private sealed class Subscription : IDisposable + { + private ReplayBase _subject; + private IScheduledObserver? _observer; + + public Subscription(ReplayBase subject, IScheduledObserver observer) + { + _subject = subject; + _observer = observer; + } + + public void Dispose() + { + var observer = Interlocked.Exchange(ref _observer, null); + if (observer == null) + { + return; + } + + observer.Dispose(); + + _subject.Unsubscribe(observer); + _subject = null!; + } + } + } + + /// + /// Original implementation of the ReplaySubject with time based operations (Scheduling, Stopwatch, buffer-by-time). + /// + private sealed class ReplayByTime : ReplayBase + { + private const int InfiniteBufferSize = int.MaxValue; + + private readonly int _bufferSize; + private readonly TimeSpan _window; + private readonly IScheduler _scheduler; + private readonly IStopwatch _stopwatch; + + private readonly Queue> _queue; + + public ReplayByTime(int bufferSize, TimeSpan window, IScheduler scheduler) + { + if (bufferSize < 0) + { + throw new ArgumentOutOfRangeException(nameof(bufferSize)); + } + + if (window < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(window)); + } + + _bufferSize = bufferSize; + _window = window; + _scheduler = scheduler ?? throw new ArgumentNullException(nameof(scheduler)); + + _stopwatch = _scheduler.StartStopwatch(); + _queue = new Queue>(); + } + + public ReplayByTime(int bufferSize, TimeSpan window) + : this(bufferSize, window, SchedulerDefaults.Iteration) + { + } + + public ReplayByTime(IScheduler scheduler) + : this(InfiniteBufferSize, TimeSpan.MaxValue, scheduler) + { + } + + public ReplayByTime(int bufferSize, IScheduler scheduler) + : this(bufferSize, TimeSpan.MaxValue, scheduler) + { + } + + public ReplayByTime(TimeSpan window, IScheduler scheduler) + : this(InfiniteBufferSize, window, scheduler) + { + } + + public ReplayByTime(TimeSpan window) + : this(InfiniteBufferSize, window, SchedulerDefaults.Iteration) + { + } + + protected override IScheduledObserver CreateScheduledObserver(IObserver observer) + { + return new ScheduledObserver(_scheduler, observer); + } + + protected override void DisposeCore() + { + _queue.Clear(); + } + + protected override void Next(T value) + { + var now = _stopwatch.Elapsed; + + _queue.Enqueue(new TimeInterval(value, now)); + } + + protected override int Replay(IObserver observer) + { + var n = _queue.Count; + + foreach (var item in _queue) + { + observer.OnNext(item.Value); + } + + return n; + } + + protected override void Trim() + { + var now = _stopwatch.Elapsed; + + while (_queue.Count > _bufferSize) + { + _queue.Dequeue(); + } + + while (_queue.Count > 0 && now.Subtract(_queue.Peek().Interval).CompareTo(_window) > 0) + { + _queue.Dequeue(); + } + } + } + + // + // Below are the non-time based implementations. + // These removed the need for the scheduler indirection, SchedulerObservers, stopwatch, TimeInterval and ensuring the scheduled observers are active after each action. + // The ReplayOne implementation also removes the need to even have a queue. + // + + private sealed class ReplayOne : ReplayBufferBase + { + private bool _hasValue; + private T? _value; + + protected override void Trim() + { + // + // No need to trim. + // + } + + protected override void Next(T value) + { + _hasValue = true; + _value = value; + } + + protected override int Replay(IObserver observer) + { + var n = 0; + + if (_hasValue) + { + n = 1; + observer.OnNext(_value!); + } + + return n; + } + + protected override void DisposeCore() + { + _value = default; + } + } + + private sealed class ReplayMany : ReplayManyBase + { + private readonly int _bufferSize; + + public ReplayMany(int bufferSize) + : base(bufferSize) + { + _bufferSize = bufferSize; + } + + protected override void Trim() + { + while (_queue.Count > _bufferSize) + { + _queue.Dequeue(); + } + } + } + + private sealed class ReplayAll : ReplayManyBase + { + public ReplayAll() + : base(0) + { + } + + protected override void Trim() + { + // + // Don't trim, keep all values. + // + } + } + + private abstract class ReplayBufferBase : ReplayBase + { + protected override IScheduledObserver CreateScheduledObserver(IObserver observer) + { + return new FastImmediateObserver(observer); + } + + protected override void DisposeCore() + { + } + } + + private abstract class ReplayManyBase : ReplayBufferBase + { + protected readonly Queue _queue; + + protected ReplayManyBase(int queueSize) + { + _queue = new Queue(Math.Min(queueSize, 64)); + } + + protected override void Next(T value) + { + _queue.Enqueue(value); + } + + protected override int Replay(IObserver observer) + { + var n = _queue.Count; + + foreach (var item in _queue) + { + observer.OnNext(item); + } + + return n; + } + + protected override void DisposeCore() + { + _queue.Clear(); + } + } + } + + /// + /// Specialized scheduled observer similar to a scheduled observer for the immediate scheduler. + /// + /// Type of the elements processed by the observer. + internal sealed class FastImmediateObserver : IScheduledObserver + { + /// + /// Gate to control ownership transfer and protect data structures. + /// + private readonly object _gate = new(); + + /// + /// Observer to forward notifications to. + /// + private volatile IObserver _observer; + + /// + /// Queue to enqueue OnNext notifications into. + /// + private Queue _queue = new(); + + /// + /// Standby queue to swap out for _queue when transferring ownership. This allows to reuse + /// queues in case of busy subjects where the initial replay doesn't suffice to catch up. + /// + private Queue? _queue2; + + /// + /// Exception passed to an OnError notification, if any. + /// + private Exception? _error; + + /// + /// Indicates whether an OnCompleted notification was received. + /// + private bool _done; + + /// + /// Indicates whether the observer is busy, i.e. some thread is actively draining the + /// notifications that were queued up. + /// + private bool _busy; + + /// + /// Indicates whether a failure occurred when the owner was draining the queue. This will + /// prevent future work to be processed. + /// + private bool _hasFaulted; + + /// + /// Creates a new scheduled observer that proxies to the specified observer. + /// + /// Observer to forward notifications to. + public FastImmediateObserver(IObserver observer) + { + _observer = observer; + } + + /// + /// Disposes the observer. + /// + public void Dispose() + { + Done(); + } + + /// + /// Notifies the observer of pending work. This will either cause the current owner to + /// process the newly enqueued notifications, or it will cause the calling thread to + /// become the owner and start processing the notification queue. + /// + public void EnsureActive() + { + EnsureActive(1); + } + + /// + /// Notifies the observer of pending work. This will either cause the current owner to + /// process the newly enqueued notifications, or it will cause the calling thread to + /// become the owner and start processing the notification queue. + /// + /// The number of enqueued notifications to process (ignored). + public void EnsureActive(int count) + { + var isOwner = false; + + lock (_gate) + { + // + // If we failed to process work in the past, we'll simply drop it. + // + if (!_hasFaulted) + { + // + // If no-one is processing the notification queue, become the owner. + // + if (!_busy) + { + isOwner = true; + _busy = true; + } + } + } + + if (isOwner) + { + while (true) + { + var queue = default(Queue); + var error = default(Exception); + var done = false; + + // + // Steal notifications from the producer side to drain them to the observer. + // + lock (_gate) + { + // + // Do we have any OnNext notifications to process? + // + if (_queue.Count > 0) + { + _queue2 ??= new Queue(); + + // + // Swap out the current queue for a fresh or recycled one. The standby + // queue is set to null; when notifications are sent out the processed + // queue will become the new standby. + // + queue = _queue; + _queue = _queue2; + _queue2 = null; + } + + // + // Do we have any terminal notifications to process? + // + if (_error != null) + { + error = _error; + } + else if (_done) + { + done = true; + } + else if (queue == null) + { + // + // No work left; quit the loop and let another thread become the + // owner in the future. + // + _busy = false; + break; + } + } + + try + { + // + // Process OnNext notifications, if any. + // + if (queue != null) + { + // + // Drain the stolen OnNext notification queue. + // + while (queue.Count > 0) + { + _observer.OnNext(queue.Dequeue()); + } + + // + // The queue is now empty, so we can reuse it by making it the standby + // queue for a future swap. + // + lock (_gate) + { + _queue2 = queue; + } + } + + // + // Process terminal notifications, if any. Notice we don't release ownership + // after processing these notifications; we simply quit from the loop. This + // will cause all processing of the scheduler observer to cease. + // + if (error != null) + { + var observer = Done(); + observer.OnError(error); + break; + } + + if (done) + { + var observer = Done(); + observer.OnCompleted(); + break; + } + } + catch + { + lock (_gate) + { + _hasFaulted = true; + _queue.Clear(); + } + + throw; + } + } + } + } + + /// + /// Enqueues an OnCompleted notification. + /// + public void OnCompleted() + { + lock (_gate) + { + if (!_hasFaulted) + { + _done = true; + } + } + } + + /// + /// Enqueues an OnError notification. + /// + /// Error of the notification. + public void OnError(Exception error) + { + lock (_gate) + { + if (!_hasFaulted) + { + _error = error; + } + } + } + + /// + /// Enqueues an OnNext notification. + /// + /// Value of the notification. + public void OnNext(T value) + { + lock (_gate) + { + if (!_hasFaulted) + { + _queue.Enqueue(value); + } + } + } + + /// + /// Terminates the observer upon receiving terminal notifications, thus preventing + /// future notifications to go out. + /// + /// Observer to send terminal notifications to. + private IObserver Done() + { + return Interlocked.Exchange(ref _observer, NopObserver.Instance); + } + } +} diff --git a/LibExternal/System.Reactive/Subjects/Subject.Extensions.cs b/LibExternal/System.Reactive/Subjects/Subject.Extensions.cs new file mode 100644 index 0000000..59779ea --- /dev/null +++ b/LibExternal/System.Reactive/Subjects/Subject.Extensions.cs @@ -0,0 +1,191 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reactive.Linq; + +namespace System.Reactive.Subjects +{ + /// + /// Provides a set of static methods for creating subjects. + /// + public static class Subject + { + /// + /// Creates a subject from the specified observer and observable. + /// + /// The type of the elements received by the observer. + /// The type of the elements produced by the observable sequence. + /// The observer used to send messages to the subject. + /// The observable used to subscribe to messages sent from the subject. + /// Subject implemented using the given observer and observable. + /// or is null. + public static ISubject Create(IObserver observer, IObservable observable) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + if (observable == null) + { + throw new ArgumentNullException(nameof(observable)); + } + + return new AnonymousSubject(observer, observable); + } + + /// + /// Creates a subject from the specified observer and observable. + /// + /// The type of the elements received by the observer and produced by the observable sequence. + /// The observer used to send messages to the subject. + /// The observable used to subscribe to messages sent from the subject. + /// Subject implemented using the given observer and observable. + /// or is null. + public static ISubject Create(IObserver observer, IObservable observable) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + if (observable == null) + { + throw new ArgumentNullException(nameof(observable)); + } + + return new AnonymousSubject(observer, observable); + } + + /// + /// Synchronizes the messages sent to the subject. + /// + /// The type of the elements received by the subject. + /// The type of the elements produced by the subject. + /// The subject to synchronize. + /// Subject whose messages are synchronized. + /// is null. + public static ISubject Synchronize(ISubject subject) + { + if (subject == null) + { + throw new ArgumentNullException(nameof(subject)); + } + + return new AnonymousSubject(Observer.Synchronize(subject), subject); + } + + /// + /// Synchronizes the messages sent to the subject. + /// + /// The type of the elements received and produced by the subject. + /// The subject to synchronize. + /// Subject whose messages are synchronized. + /// is null. + public static ISubject Synchronize(ISubject subject) + { + if (subject == null) + { + throw new ArgumentNullException(nameof(subject)); + } + + return new AnonymousSubject(Observer.Synchronize(subject), subject); + } + + /// + /// Synchronizes the messages sent to the subject and notifies observers on the specified scheduler. + /// + /// The type of the elements received by the subject. + /// The type of the elements produced by the subject. + /// The subject to synchronize. + /// Scheduler to notify observers on. + /// Subject whose messages are synchronized and whose observers are notified on the given scheduler. + /// or is null. + public static ISubject Synchronize(ISubject subject, IScheduler scheduler) + { + if (subject == null) + { + throw new ArgumentNullException(nameof(subject)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return new AnonymousSubject(Observer.Synchronize(subject), subject.ObserveOn(scheduler)); + } + + /// + /// Synchronizes the messages sent to the subject and notifies observers on the specified scheduler. + /// + /// The type of the elements received and produced by the subject. + /// The subject to synchronize. + /// Scheduler to notify observers on. + /// Subject whose messages are synchronized and whose observers are notified on the given scheduler. + /// or is null. + public static ISubject Synchronize(ISubject subject, IScheduler scheduler) + { + if (subject == null) + { + throw new ArgumentNullException(nameof(subject)); + } + + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return new AnonymousSubject(Observer.Synchronize(subject), subject.ObserveOn(scheduler)); + } + + private class AnonymousSubject : ISubject + { + private readonly IObserver _observer; + private readonly IObservable _observable; + + public AnonymousSubject(IObserver observer, IObservable observable) + { + _observer = observer; + _observable = observable; + } + + public void OnCompleted() => _observer.OnCompleted(); + + public void OnError(Exception error) + { + if (error == null) + { + throw new ArgumentNullException(nameof(error)); + } + + _observer.OnError(error); + } + + public void OnNext(T value) => _observer.OnNext(value); + + public IDisposable Subscribe(IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + // + // [OK] Use of unsafe Subscribe: non-pretentious wrapping of an observable sequence. + // + return _observable.Subscribe/*Unsafe*/(observer); + } + } + + private sealed class AnonymousSubject : AnonymousSubject, ISubject + { + public AnonymousSubject(IObserver observer, IObservable observable) + : base(observer, observable) + { + } + } + } +} diff --git a/LibExternal/System.Reactive/Subjects/Subject.cs b/LibExternal/System.Reactive/Subjects/Subject.cs new file mode 100644 index 0000000..53a464d --- /dev/null +++ b/LibExternal/System.Reactive/Subjects/Subject.cs @@ -0,0 +1,300 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Disposables; +using System.Threading; + +namespace System.Reactive.Subjects +{ + /// + /// Represents an object that is both an observable sequence as well as an observer. + /// Each notification is broadcasted to all subscribed observers. + /// + /// The type of the elements processed by the subject. + public sealed class Subject : SubjectBase + { + #region Fields + + private SubjectDisposable[] _observers; + private Exception? _exception; +#pragma warning disable CA1825 // (Avoid zero-length array allocations.) The identity of these arrays matters, so we can't use the shared Array.Empty() instance + private static readonly SubjectDisposable[] Terminated = new SubjectDisposable[0]; + private static readonly SubjectDisposable[] Disposed = new SubjectDisposable[0]; +#pragma warning restore CA1825 + + #endregion + + #region Constructors + + /// + /// Creates a subject. + /// + public Subject() => _observers = Array.Empty(); + + #endregion + + #region Properties + + /// + /// Indicates whether the subject has observers subscribed to it. + /// + public override bool HasObservers => Volatile.Read(ref _observers).Length != 0; + + /// + /// Indicates whether the subject has been disposed. + /// + public override bool IsDisposed => Volatile.Read(ref _observers) == Disposed; + + #endregion + + #region Methods + + #region IObserver implementation + + private static void ThrowDisposed() => throw new ObjectDisposedException(string.Empty); + + /// + /// Notifies all subscribed observers about the end of the sequence. + /// + public override void OnCompleted() + { + for (; ; ) + { + var observers = Volatile.Read(ref _observers); + + if (observers == Disposed) + { + _exception = null; + ThrowDisposed(); + break; + } + + if (observers == Terminated) + { + break; + } + + if (Interlocked.CompareExchange(ref _observers, Terminated, observers) == observers) + { + foreach (var observer in observers) + { + observer.Observer?.OnCompleted(); + } + + break; + } + } + } + + /// + /// Notifies all subscribed observers about the specified exception. + /// + /// The exception to send to all currently subscribed observers. + /// is null. + public override void OnError(Exception error) + { + if (error == null) + { + throw new ArgumentNullException(nameof(error)); + } + + for (; ; ) + { + var observers = Volatile.Read(ref _observers); + + if (observers == Disposed) + { + _exception = null; + ThrowDisposed(); + break; + } + + if (observers == Terminated) + { + break; + } + + _exception = error; + + if (Interlocked.CompareExchange(ref _observers, Terminated, observers) == observers) + { + foreach (var observer in observers) + { + observer.Observer?.OnError(error); + } + + break; + } + } + } + + /// + /// Notifies all subscribed observers about the arrival of the specified element in the sequence. + /// + /// The value to send to all currently subscribed observers. + public override void OnNext(T value) + { + var observers = Volatile.Read(ref _observers); + + if (observers == Disposed) + { + _exception = null; + ThrowDisposed(); + return; + } + + foreach (var observer in observers) + { + observer.Observer?.OnNext(value); + } + } + + #endregion + + #region IObservable implementation + + /// + /// Subscribes an observer to the subject. + /// + /// Observer to subscribe to the subject. + /// Disposable object that can be used to unsubscribe the observer from the subject. + /// is null. + public override IDisposable Subscribe(IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + var disposable = default(SubjectDisposable); + for (; ; ) + { + var observers = Volatile.Read(ref _observers); + + if (observers == Disposed) + { + _exception = null; + ThrowDisposed(); + + break; + } + + if (observers == Terminated) + { + var ex = _exception; + + if (ex != null) + { + observer.OnError(ex); + } + else + { + observer.OnCompleted(); + } + + break; + } + + disposable ??= new SubjectDisposable(this, observer); + + var n = observers.Length; + var b = new SubjectDisposable[n + 1]; + + Array.Copy(observers, 0, b, 0, n); + + b[n] = disposable; + + if (Interlocked.CompareExchange(ref _observers, b, observers) == observers) + { + return disposable; + } + } + + return Disposable.Empty; + } + + private void Unsubscribe(SubjectDisposable observer) + { + for (; ; ) + { + var a = Volatile.Read(ref _observers); + var n = a.Length; + + if (n == 0) + { + break; + } + + var j = Array.IndexOf(a, observer); + + if (j < 0) + { + break; + } + + SubjectDisposable[] b; + + if (n == 1) + { + b = Array.Empty(); + } + else + { + b = new SubjectDisposable[n - 1]; + + Array.Copy(a, 0, b, 0, j); + Array.Copy(a, j + 1, b, j, n - j - 1); + } + + if (Interlocked.CompareExchange(ref _observers, b, a) == a) + { + break; + } + } + } + + private sealed class SubjectDisposable : IDisposable + { + private Subject _subject; + private volatile IObserver? _observer; + + public SubjectDisposable(Subject subject, IObserver observer) + { + _subject = subject; + _observer = observer; + } + + public IObserver? Observer => _observer; + + public void Dispose() + { + var observer = Interlocked.Exchange(ref _observer, null); + if (observer == null) + { + return; + } + + _subject.Unsubscribe(this); + _subject = null!; + } + } + + #endregion + + #region IDisposable implementation + + /// + /// Releases all resources used by the current instance of the class and unsubscribes all observers. + /// + public override void Dispose() + { + Interlocked.Exchange(ref _observers, Disposed); + _exception = null; + } + + #endregion + + #endregion + } +} diff --git a/LibExternal/System.Reactive/Subjects/SubjectBase.cs b/LibExternal/System.Reactive/Subjects/SubjectBase.cs new file mode 100644 index 0000000..fd14a4d --- /dev/null +++ b/LibExternal/System.Reactive/Subjects/SubjectBase.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Subjects +{ + /// + /// Base class for objects that are both an observable sequence as well as an observer. + /// + /// The type of the elements processed by the subject. +#pragma warning disable CA1063 // (Overridable IDisposable.) This analyzer wants us to make breaking changes to its public API, which we can't do. + public abstract class SubjectBase : ISubject, IDisposable + { + /// + /// Indicates whether the subject has observers subscribed to it. + /// + public abstract bool HasObservers { get; } + + /// + /// Indicates whether the subject has been disposed. + /// + public abstract bool IsDisposed { get; } + + /// + /// Releases all resources used by the current instance of the subject and unsubscribes all observers. + /// + public abstract void Dispose(); + + /// + /// Notifies all subscribed observers about the end of the sequence. + /// + public abstract void OnCompleted(); + + /// + /// Notifies all subscribed observers about the specified exception. + /// + /// The exception to send to all currently subscribed observers. + /// is null. +#pragma warning disable CA1716 // (Identifiers should not match keywords.) This has been the name for years, so the (admittedly small) risk from changing it doesn't seem to offer a meaningful benefit. + public abstract void OnError(Exception error); +#pragma warning restore CA1716 + + /// + /// Notifies all subscribed observers about the arrival of the specified element in the sequence. + /// + /// The value to send to all currently subscribed observers. + public abstract void OnNext(T value); + + /// + /// Subscribes an observer to the subject. + /// + /// Observer to subscribe to the subject. + /// Disposable object that can be used to unsubscribe the observer from the subject. + /// is null. + public abstract IDisposable Subscribe(IObserver observer); + } +#pragma warning restore CA1063 +} diff --git a/Kit.Core.Helpers/Kit.Core.Helpers.csproj b/LibExternal/System.Reactive/System.Reactive.csproj similarity index 82% rename from Kit.Core.Helpers/Kit.Core.Helpers.csproj rename to LibExternal/System.Reactive/System.Reactive.csproj index fa71b7a..30402ac 100644 --- a/Kit.Core.Helpers/Kit.Core.Helpers.csproj +++ b/LibExternal/System.Reactive/System.Reactive.csproj @@ -1,4 +1,4 @@ - + net8.0 diff --git a/LibExternal/System.Reactive/TaskObservable.cs b/LibExternal/System.Reactive/TaskObservable.cs new file mode 100644 index 0000000..7968e7b --- /dev/null +++ b/LibExternal/System.Reactive/TaskObservable.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Runtime.CompilerServices; + +namespace System.Reactive +{ + /// + /// Extension of the interface compatible with async method return types. + /// + /// + /// This class implements a "task-like" type that can be used as the return type of an asynchronous + /// method in C# 7.0 and beyond. For example: + /// + /// async ITaskObservable<int> RxAsync() + /// { + /// var res = await Observable.Return(21).Delay(TimeSpan.FromSeconds(1)); + /// return res * 2; + /// } + /// + /// + /// The type of the elements in the sequence. + [AsyncMethodBuilder(typeof(TaskObservableMethodBuilder<>))] + public interface ITaskObservable : IObservable + { + // NB: An interface type is preferred to enable the use of covariance. + + /// + /// Gets an awaiter that can be used to await the eventual completion of the observable sequence. + /// + /// An awaiter that can be used to await the eventual completion of the observable sequence. + ITaskObservableAwaiter GetAwaiter(); + } + + /// + /// Interface representing an awaiter for an . + /// + /// The type of the elements in the sequence. + public interface ITaskObservableAwaiter : INotifyCompletion + { + /// + /// Gets a Boolean indicating whether the observable sequence has completed. + /// + bool IsCompleted { get; } + + /// + /// Gets the result produced by the observable sequence. + /// + /// The result produced by the observable sequence. + T GetResult(); + } +} diff --git a/LibExternal/System.Reactive/Threading/Tasks/NamespaceDoc.cs b/LibExternal/System.Reactive/Threading/Tasks/NamespaceDoc.cs new file mode 100644 index 0000000..823e20f --- /dev/null +++ b/LibExternal/System.Reactive/Threading/Tasks/NamespaceDoc.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive.Threading.Tasks +{ + /// + /// The System.Reactive.Threading.Tasks namespace contains helpers for the conversion between tasks and observable sequences. + /// + [Runtime.CompilerServices.CompilerGenerated] + internal class NamespaceDoc + { + } +} diff --git a/LibExternal/System.Reactive/Threading/Tasks/TaskObservableExtensions.cs b/LibExternal/System.Reactive/Threading/Tasks/TaskObservableExtensions.cs new file mode 100644 index 0000000..a2d0843 --- /dev/null +++ b/LibExternal/System.Reactive/Threading/Tasks/TaskObservableExtensions.cs @@ -0,0 +1,639 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Reactive.Concurrency; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Reactive.Linq.ObservableImpl; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Reactive.Threading.Tasks +{ + /// + /// Provides a set of static methods for converting tasks to observable sequences. + /// + public static class TaskObservableExtensions + { + private sealed class SlowTaskObservable : IObservable + { + private readonly Task _task; + private readonly IScheduler? _scheduler; + private readonly bool _ignoreExceptionsAfterUnsubscribe; + + public SlowTaskObservable(Task task, IScheduler? scheduler, bool ignoreExceptionsAfterUnsubscribe) + { + _task = task; + _scheduler = scheduler; + _ignoreExceptionsAfterUnsubscribe = ignoreExceptionsAfterUnsubscribe; + } + + public IDisposable Subscribe(IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + var cts = new CancellationDisposable(); + var options = GetTaskContinuationOptions(_scheduler); + + if (_scheduler == null) + { + _task.ContinueWith(static (t, subjectObject) => t.EmitTaskResult((IObserver)subjectObject!), observer, cts.Token, options, TaskScheduler.Current); + } + else + { + _task.ContinueWithState( + static (task, tuple) => tuple.scheduler.ScheduleAction( + (task, tuple.observer), + static tuple2 => tuple2.task.EmitTaskResult(tuple2.observer)), + (scheduler: _scheduler, observer), + options, + cts.Token); + } + + if (_ignoreExceptionsAfterUnsubscribe) + { + _task.ContinueWith(t => _ = t.Exception, TaskContinuationOptions.OnlyOnFaulted); + } + + return cts; + } + } + + private sealed class SlowTaskObservable : IObservable + { + private readonly Task _task; + private readonly IScheduler? _scheduler; + private readonly bool _ignoreExceptionsAfterUnsubscribe; + + public SlowTaskObservable(Task task, IScheduler? scheduler, bool ignoreExceptionsAfterUnsubscribe) + { + _task = task; + _scheduler = scheduler; + _ignoreExceptionsAfterUnsubscribe = ignoreExceptionsAfterUnsubscribe; + } + + public IDisposable Subscribe(IObserver observer) + { + if (observer == null) + { + throw new ArgumentNullException(nameof(observer)); + } + + var cts = new CancellationDisposable(); + var options = GetTaskContinuationOptions(_scheduler); + + if (_scheduler == null) + { + _task.ContinueWith(static (t, subjectObject) => t.EmitTaskResult((IObserver)subjectObject!), observer, cts.Token, options, TaskScheduler.Current); + } + else + { + _task.ContinueWithState( + static (task, tuple) => tuple.scheduler.ScheduleAction( + (task, tuple.observer), + static tuple2 => tuple2.task.EmitTaskResult(tuple2.observer)), + (scheduler: _scheduler, observer), + options, + cts.Token); + } + + if (_ignoreExceptionsAfterUnsubscribe) + { + _task.ContinueWith(t => _ = t.Exception, TaskContinuationOptions.OnlyOnFaulted); + } + + return cts; + } + } + + /// + /// Returns an observable sequence that signals when the task completes. + /// + /// Task to convert to an observable sequence. + /// An observable sequence that produces a unit value when the task completes, or propagates the exception produced by the task. + /// is null. + /// If the specified task object supports cancellation, consider using instead. + public static IObservable ToObservable(this Task task) + { + if (task == null) + { + throw new ArgumentNullException(nameof(task)); + } + + return ToObservableImpl(task, scheduler: null, ignoreExceptionsAfterUnsubscribe: false); + } + + /// + /// Returns an observable sequence that signals when the task completes. + /// + /// Task to convert to an observable sequence. + /// Scheduler on which to notify observers about completion, cancellation or failure. + /// An observable sequence that produces a unit value when the task completes, or propagates the exception produced by the task. + /// is null or is null. + /// If the specified task object supports cancellation, consider using instead. + public static IObservable ToObservable(this Task task, IScheduler scheduler) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return ToObservable(task, new TaskObservationOptions(scheduler, ignoreExceptionsAfterUnsubscribe: false)); + } + + /// + /// Returns an observable sequence that signals when the task completes. + /// + /// Task to convert to an observable sequence. + /// Controls how the tasks's progress is observed. + /// An observable sequence that produces a unit value when the task completes, or propagates the exception produced by the task. + /// is null. + /// If the specified task object supports cancellation, consider using instead. + public static IObservable ToObservable(this Task task, TaskObservationOptions options) + { + if (task == null) + { + throw new ArgumentNullException(nameof(task)); + } + + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + return ToObservableImpl(task, options.Scheduler, options.IgnoreExceptionsAfterUnsubscribe); + } + + internal static IObservable ToObservable(this Task task, TaskObservationOptions.Value options) + { + if (task == null) + { + throw new ArgumentNullException(nameof(task)); + } + + return ToObservableImpl(task, options.Scheduler, options.IgnoreExceptionsAfterUnsubscribe); + } + + + private static IObservable ToObservableImpl(Task task, IScheduler? scheduler, bool ignoreExceptionsAfterUnsubscribe) + { + if (task.IsCompleted) + { + scheduler ??= ImmediateScheduler.Instance; + + return task.Status switch + { + TaskStatus.Faulted => new Throw(task.GetSingleException(), scheduler), + TaskStatus.Canceled => new Throw(new TaskCanceledException(task), scheduler), + _ => new Return(Unit.Default, scheduler) + }; + } + + return new SlowTaskObservable(task, scheduler, ignoreExceptionsAfterUnsubscribe); + } + + private static void EmitTaskResult(this Task task, IObserver subject) + { + switch (task.Status) + { + case TaskStatus.RanToCompletion: + subject.OnNext(Unit.Default); + subject.OnCompleted(); + break; + case TaskStatus.Faulted: + subject.OnError(task.GetSingleException()); + break; + case TaskStatus.Canceled: + subject.OnError(new TaskCanceledException(task)); + break; + } + } + + internal static IDisposable Subscribe(this Task task, IObserver observer) + { + if (task.IsCompleted) + { + task.EmitTaskResult(observer); + return Disposable.Empty; + } + + var cts = new CancellationDisposable(); + + task.ContinueWith( + static (t, observerObject) => t.EmitTaskResult((IObserver)observerObject!), + observer, + cts.Token, + TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Current); + + return cts; + } + + /// + /// Returns an observable sequence that propagates the result of the task. + /// + /// The type of the result produced by the task. + /// Task to convert to an observable sequence. + /// An observable sequence that produces the task's result, or propagates the exception produced by the task. + /// is null. + /// If the specified task object supports cancellation, consider using instead. + public static IObservable ToObservable(this Task task) + { + if (task == null) + { + throw new ArgumentNullException(nameof(task)); + } + + return ToObservableImpl(task, scheduler: null, ignoreExceptionsAfterUnsubscribe: false); + } + + /// + /// Returns an observable sequence that propagates the result of the task. + /// + /// The type of the result produced by the task. + /// Task to convert to an observable sequence. + /// Scheduler on which to notify observers about completion, cancellation or failure. + /// An observable sequence that produces the task's result, or propagates the exception produced by the task. + /// is null or is null. + /// If the specified task object supports cancellation, consider using instead. + public static IObservable ToObservable(this Task task, IScheduler scheduler) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + + return ToObservable(task, new TaskObservationOptions(scheduler, ignoreExceptionsAfterUnsubscribe: false)); + } + + /// + /// Returns an observable sequence that propagates the result of the task. + /// + /// The type of the result produced by the task. + /// Task to convert to an observable sequence. + /// Controls how the tasks's progress is observed. + /// An observable sequence that produces the task's result, or propagates the exception produced by the task. + /// is null. + /// If the specified task object supports cancellation, consider using instead. + public static IObservable ToObservable(this Task task, TaskObservationOptions options) + { + if (task == null) + { + throw new ArgumentNullException(nameof(task)); + } + + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + return ToObservableImpl(task, options.Scheduler, options.IgnoreExceptionsAfterUnsubscribe); + } + + internal static IObservable ToObservable(this Task task, TaskObservationOptions.Value options) + { + if (task == null) + { + throw new ArgumentNullException(nameof(task)); + } + + return ToObservableImpl(task, options.Scheduler, options.IgnoreExceptionsAfterUnsubscribe); + } + + private static IObservable ToObservableImpl(Task task, IScheduler? scheduler, bool ignoreExceptionsAfterUnsubscribe) + { + if (task.IsCompleted) + { + scheduler ??= ImmediateScheduler.Instance; + + return task.Status switch + { + TaskStatus.Faulted => new Throw(task.GetSingleException(), scheduler), + TaskStatus.Canceled => new Throw(new TaskCanceledException(task), scheduler), + _ => new Return(task.Result, scheduler) + }; + } + + return new SlowTaskObservable(task, scheduler, ignoreExceptionsAfterUnsubscribe); + } + + private static void EmitTaskResult(this Task task, IObserver subject) + { + switch (task.Status) + { + case TaskStatus.RanToCompletion: + subject.OnNext(task.Result); + subject.OnCompleted(); + break; + case TaskStatus.Faulted: + subject.OnError(task.GetSingleException()); + break; + case TaskStatus.Canceled: + subject.OnError(new TaskCanceledException(task)); + break; + } + } + + private static TaskContinuationOptions GetTaskContinuationOptions(IScheduler? scheduler) + { + var options = TaskContinuationOptions.None; + + if (scheduler != null) + { + // + // We explicitly don't special-case the immediate scheduler here. If the user asks for a + // synchronous completion, we'll try our best. However, there's no guarantee due to the + // internal stack probing in the TPL, which may cause asynchronous completion on a thread + // pool thread in order to avoid stack overflows. Therefore we can only attempt to be more + // efficient in the case where the user specified a scheduler, hence we know that the + // continuation will trigger a scheduling operation. In case of the immediate scheduler, + // it really becomes "immediate scheduling" wherever the TPL decided to run the continuation, + // i.e. not necessarily where the task was completed from. + // + options |= TaskContinuationOptions.ExecuteSynchronously; + } + + return options; + } + + internal static IDisposable Subscribe(this Task task, IObserver observer) + { + if (task.IsCompleted) + { + task.EmitTaskResult(observer); + return Disposable.Empty; + } + + var cts = new CancellationDisposable(); + + task.ContinueWith( + static (t, observerObject) => t.EmitTaskResult((IObserver)observerObject!), + observer, + cts.Token, + TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Current); + + return cts; + } + + /// + /// Returns a task that will receive the last value or the exception produced by the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to convert to a task. + /// A task that will receive the last element or the exception produced by the observable sequence. + /// is null. + public static Task ToTask(this IObservable observable) + { + if (observable == null) + { + throw new ArgumentNullException(nameof(observable)); + } + + return observable.ToTask(new CancellationToken(), state: null); + } + + + /// + /// Returns a task that will receive the last value or the exception produced by the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to convert to a task. + /// The scheduler used for overriding where the task completion signals will be issued. + /// A task that will receive the last element or the exception produced by the observable sequence. + /// or is null. + public static Task ToTask(this IObservable observable, IScheduler scheduler) + { + return observable.ToTask().ContinueOnScheduler(scheduler); + } + + /// + /// Returns a task that will receive the last value or the exception produced by the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to convert to a task. + /// The state to use as the underlying task's AsyncState. + /// A task that will receive the last element or the exception produced by the observable sequence. + /// is null. + public static Task ToTask(this IObservable observable, object? state) + { + if (observable == null) + { + throw new ArgumentNullException(nameof(observable)); + } + + return observable.ToTask(new CancellationToken(), state); + } + + /// + /// Returns a task that will receive the last value or the exception produced by the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to convert to a task. + /// The state to use as the underlying task's AsyncState. + /// The scheduler used for overriding where the task completion signals will be issued. + /// A task that will receive the last element or the exception produced by the observable sequence. + /// or is null. + public static Task ToTask(this IObservable observable, object? state, IScheduler scheduler) + { + return observable.ToTask(new CancellationToken(), state).ContinueOnScheduler(scheduler); + } + + /// + /// Returns a task that will receive the last value or the exception produced by the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to convert to a task. + /// Cancellation token that can be used to cancel the task, causing unsubscription from the observable sequence. + /// A task that will receive the last element or the exception produced by the observable sequence. + /// is null. + public static Task ToTask(this IObservable observable, CancellationToken cancellationToken) + { + if (observable == null) + { + throw new ArgumentNullException(nameof(observable)); + } + + return observable.ToTask(cancellationToken, state: null); + } + + /// + /// Returns a task that will receive the last value or the exception produced by the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to convert to a task. + /// Cancellation token that can be used to cancel the task, causing unsubscription from the observable sequence. + /// The scheduler used for overriding where the task completion signals will be issued. + /// A task that will receive the last element or the exception produced by the observable sequence. + /// or is null. +#pragma warning disable CA1068 // (CancellationToken parameters must come last.) Would be a breaking change. + public static Task ToTask(this IObservable observable, CancellationToken cancellationToken, IScheduler scheduler) +#pragma warning restore CA1068 + { + return observable.ToTask(cancellationToken, state: null).ContinueOnScheduler(scheduler); + } + + internal static Task ContinueOnScheduler(this Task task, IScheduler scheduler) + { + if (scheduler == null) + { + throw new ArgumentNullException(nameof(scheduler)); + } + var tcs = new TaskCompletionSource(task.AsyncState); + task.ContinueWith( + static (t, o) => + { + var (scheduler, tcs) = ((IScheduler, TaskCompletionSource))o!; + + scheduler.ScheduleAction((t, tcs), static state => + { + if (state.t.IsCanceled) + { + state.tcs.TrySetCanceled(new TaskCanceledException(state.t).CancellationToken); + } + else if (state.t.IsFaulted) + { + state.tcs.TrySetException(state.t.GetSingleException()); + } + else + { + state.tcs.TrySetResult(state.t.Result); + } + }); + }, + (scheduler, tcs), + TaskContinuationOptions.ExecuteSynchronously); + return tcs.Task; + } + + private sealed class ToTaskObserver : SafeObserver + { + private readonly CancellationToken _ct; + private readonly TaskCompletionSource _tcs; + private readonly CancellationTokenRegistration _ctr; + + private bool _hasValue; + private TResult? _lastValue; + + public ToTaskObserver(TaskCompletionSource tcs, CancellationToken ct) + { + _ct = ct; + _tcs = tcs; + + if (ct.CanBeCanceled) + { + _ctr = ct.Register(static @this => ((ToTaskObserver)@this!).Cancel(), this); + } + } + + public override void OnNext(TResult value) + { + _hasValue = true; + _lastValue = value; + } + + public override void OnError(Exception error) + { + _tcs.TrySetException(error); + + _ctr.Dispose(); // no null-check needed (struct) + Dispose(); + } + + public override void OnCompleted() + { + if (_hasValue) + { + _tcs.TrySetResult(_lastValue!); + } + else + { + try + { + throw new InvalidOperationException(Strings_Linq.NO_ELEMENTS); + } + catch (Exception e) + { + _tcs.TrySetException(e); + } + } + + _ctr.Dispose(); // no null-check needed (struct) + Dispose(); + } + + private void Cancel() + { + Dispose(); + _tcs.TrySetCanceled(_ct); + } + } + + /// + /// Returns a task that will receive the last value or the exception produced by the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to convert to a task. + /// Cancellation token that can be used to cancel the task, causing unsubscription from the observable sequence. + /// The state to use as the underlying task's . + /// A task that will receive the last element or the exception produced by the observable sequence. + /// is null. +#pragma warning disable CA1068 // (CancellationToken parameters must come last.) Would be a breaking change. + public static Task ToTask(this IObservable observable, CancellationToken cancellationToken, object? state) +#pragma warning restore CA1068 + { + if (observable == null) + { + throw new ArgumentNullException(nameof(observable)); + } + + var tcs = new TaskCompletionSource(state); + + var taskCompletionObserver = new ToTaskObserver(tcs, cancellationToken); + + // + // Subtle race condition: if the source completes before we reach the line below, the SingleAssigmentDisposable + // will already have been disposed. Upon assignment, the disposable resource being set will be disposed on the + // spot, which may throw an exception. + // + try + { + // + // [OK] Use of unsafe Subscribe: we're catching the exception here to set the TaskCompletionSource. + // + // Notice we could use a safe subscription to route errors through OnError, but we still need the + // exception handling logic here for the reason explained above. We cannot afford to throw here + // and as a result never set the TaskCompletionSource, so we tunnel everything through here. + // + taskCompletionObserver.SetResource(observable.Subscribe/*Unsafe*/(taskCompletionObserver)); + } + catch (Exception ex) + { + tcs.TrySetException(ex); + } + + return tcs.Task; + } + + /// + /// Returns a task that will receive the last value or the exception produced by the observable sequence. + /// + /// The type of the elements in the source sequence. + /// Observable sequence to convert to a task. + /// Cancellation token that can be used to cancel the task, causing unsubscription from the observable sequence. + /// The state to use as the underlying task's . + /// The scheduler used for overriding where the task completion signals will be issued. + /// A task that will receive the last element or the exception produced by the observable sequence. + /// or is null. +#pragma warning disable CA1068 // (CancellationToken parameters must come last.) Would be a breaking change. + public static Task ToTask(this IObservable observable, CancellationToken cancellationToken, object? state, IScheduler scheduler) +#pragma warning restore CA1068 + { + return observable.ToTask(cancellationToken, state).ContinueOnScheduler(scheduler); + } + } +} diff --git a/LibExternal/System.Reactive/TimeInterval.cs b/LibExternal/System.Reactive/TimeInterval.cs new file mode 100644 index 0000000..96d1b59 --- /dev/null +++ b/LibExternal/System.Reactive/TimeInterval.cs @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Globalization; + +namespace System.Reactive +{ + /// + /// Represents a value associated with time interval information. + /// The time interval can represent the time it took to produce the value, the interval relative to a previous value, the value's delivery time relative to a base, etc. + /// + /// The type of the value being annotated with time interval information. + [Serializable] + public readonly struct TimeInterval : IEquatable> + { + /// + /// Constructs a time interval value. + /// + /// The value to be annotated with a time interval. + /// Time interval associated with the value. + public TimeInterval(T value, TimeSpan interval) + { + Interval = interval; + Value = value; + } + + /// + /// Gets the value. + /// + public T Value { get; } + + /// + /// Gets the interval. + /// + public TimeSpan Interval { get; } + + /// + /// Deconstructs the time interval value into a value and a time interval. + /// + /// The value. + /// Time interval associated with the value. + public void Deconstruct(out T value, out TimeSpan interval) => (value, interval) = (Value, Interval); + + /// + /// Determines whether the current value has the same and as a specified value. + /// + /// An object to compare to the current value. + /// true if both values have the same and ; otherwise, false. + public bool Equals(TimeInterval other) => other.Interval.Equals(Interval) && EqualityComparer.Default.Equals(Value, other.Value); + + /// + /// Determines whether the two specified values have the same and . + /// + /// The first value to compare. + /// The second value to compare. + /// true if the first value has the same and as the second value; otherwise, false. + public static bool operator ==(TimeInterval first, TimeInterval second) => first.Equals(second); + + /// + /// Determines whether the two specified values don't have the same and . + /// + /// The first value to compare. + /// The second value to compare. + /// true if the first value has a different or as the second value; otherwise, false. + public static bool operator !=(TimeInterval first, TimeInterval second) => !first.Equals(second); + + /// + /// Determines whether the specified System.Object is equal to the current . + /// + /// The System.Object to compare with the current . + /// true if the specified System.Object is equal to the current ; otherwise, false. + public override bool Equals(object? obj) => obj is TimeInterval interval && Equals(interval); + + /// + /// Returns the hash code for the current value. + /// + /// A hash code for the current value. + public override int GetHashCode() + { + // TODO: Use proper hash code combiner. + return Interval.GetHashCode() ^ (Value?.GetHashCode() ?? 1963); + } + + /// + /// Returns a string representation of the current value. + /// + /// String representation of the current value. + public override string ToString() => string.Format(CultureInfo.CurrentCulture, "{0}@{1}", Value, Interval); + } +} diff --git a/LibExternal/System.Reactive/Timestamped.cs b/LibExternal/System.Reactive/Timestamped.cs new file mode 100644 index 0000000..73b6342 --- /dev/null +++ b/LibExternal/System.Reactive/Timestamped.cs @@ -0,0 +1,111 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Globalization; + +namespace System.Reactive +{ + /// + /// Represents value with a timestamp on it. + /// The timestamp typically represents the time the value was received, using an IScheduler's clock to obtain the current time. + /// + /// The type of the value being timestamped. + [Serializable] + [Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Timestamped", Justification = "Reviewed and agreed upon.")] + public readonly struct Timestamped : IEquatable> + { + /// + /// Constructs a timestamped value. + /// + /// The value to be annotated with a timestamp. + /// Timestamp associated with the value. + public Timestamped(T value, DateTimeOffset timestamp) + { + Timestamp = timestamp; + Value = value; + } + + /// + /// Gets the value. + /// + public T Value { get; } + + /// + /// Gets the timestamp. + /// + public DateTimeOffset Timestamp { get; } + + /// + /// Deconstructs the timestamped value into a value and a timestamp. + /// + /// The value. + /// Timestamp associated with the value. + public void Deconstruct(out T value, out DateTimeOffset timestamp) => (value, timestamp) = (Value, Timestamp); + + /// + /// Determines whether the current value has the same and as a specified value. + /// + /// An object to compare to the current value. + /// true if both values have the same and ; otherwise, false. + public bool Equals(Timestamped other) => other.Timestamp.Equals(Timestamp) && EqualityComparer.Default.Equals(Value, other.Value); + + /// + /// Determines whether the two specified values have the same and . + /// + /// The first value to compare. + /// The second value to compare. + /// true if the first value has the same and as the second value; otherwise, false. + public static bool operator ==(Timestamped first, Timestamped second) => first.Equals(second); + + /// + /// Determines whether the two specified values don't have the same and . + /// + /// The first value to compare. + /// The second value to compare. + /// true if the first value has a different or as the second value; otherwise, false. + public static bool operator !=(Timestamped first, Timestamped second) => !first.Equals(second); + + /// + /// Determines whether the specified System.Object is equal to the current . + /// + /// The System.Object to compare with the current . + /// true if the specified System.Object is equal to the current ; otherwise, false. + public override bool Equals(object? obj) => obj is Timestamped timestamped && Equals(timestamped); + + /// + /// Returns the hash code for the current value. + /// + /// A hash code for the current value. + public override int GetHashCode() + { + // TODO: Use proper hash code combiner. + return Timestamp.GetHashCode() ^ (Value?.GetHashCode() ?? 1979); + } + + /// + /// Returns a string representation of the current value. + /// + /// String representation of the current value. + public override string ToString() => string.Format(CultureInfo.CurrentCulture, "{0}@{1}", Value, Timestamp); + } + + /// + /// A helper class with a factory method for creating instances. + /// + public static class Timestamped + { + /// + /// Creates an instance of a . This is syntactic sugar that uses type inference + /// to avoid specifying a type in a constructor call, which is very useful when using anonymous types. + /// + /// The value to be annotated with a timestamp. + /// Timestamp associated with the value. + /// Creates a new timestamped value. + public static Timestamped Create(T value, DateTimeOffset timestamp) + { + return new Timestamped(value, timestamp); + } + } +} diff --git a/LibExternal/System.Reactive/Unit.cs b/LibExternal/System.Reactive/Unit.cs new file mode 100644 index 0000000..b523948 --- /dev/null +++ b/LibExternal/System.Reactive/Unit.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +namespace System.Reactive +{ + /// + /// Represents a type with a single value. This type is often used to denote the successful completion of a void-returning method (C#) or a Sub procedure (Visual Basic). + /// + [Serializable] + public readonly struct Unit : IEquatable + { + /// + /// Determines whether the specified value is equal to the current . Because has a single value, this always returns true. + /// + /// An object to compare to the current value. + /// Because has a single value, this always returns true. + public bool Equals(Unit other) => true; + + /// + /// Determines whether the specified System.Object is equal to the current . + /// + /// The System.Object to compare with the current . + /// true if the specified System.Object is a value; otherwise, false. + public override bool Equals(object? obj) => obj is Unit; + + /// + /// Returns the hash code for the current value. + /// + /// A hash code for the current value. + public override int GetHashCode() => 0; + + /// + /// Returns a string representation of the current value. + /// + /// String representation of the current value. + public override string ToString() => "()"; + + /// + /// Determines whether the two specified values are equal. Because has a single value, this always returns true. + /// + /// The first value to compare. + /// The second value to compare. + /// Because has a single value, this always returns true. +#pragma warning disable IDE0060 // (Remove unused parameter.) Required part of public API + public static bool operator ==(Unit first, Unit second) => true; + + /// + /// Determines whether the two specified values are not equal. Because has a single value, this always returns false. + /// + /// The first value to compare. + /// The second value to compare. + /// Because has a single value, this always returns false. + public static bool operator !=(Unit first, Unit second) => false; +#pragma warning restore IDE0060 + + /// + /// Gets the single value. + /// + public static Unit Default => default; + } +} diff --git a/LibExternal/System.Reactive/bin/Debug/net8.0/System.Reactive.deps.json b/LibExternal/System.Reactive/bin/Debug/net8.0/System.Reactive.deps.json new file mode 100644 index 0000000..c049915 --- /dev/null +++ b/LibExternal/System.Reactive/bin/Debug/net8.0/System.Reactive.deps.json @@ -0,0 +1,23 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v8.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v8.0": { + "System.Reactive/1.0.0": { + "runtime": { + "System.Reactive.dll": {} + } + } + } + }, + "libraries": { + "System.Reactive/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/LibExternal/System.Reactive/bin/Debug/net8.0/System.Reactive.dll b/LibExternal/System.Reactive/bin/Debug/net8.0/System.Reactive.dll new file mode 100644 index 0000000..7b8ccea Binary files /dev/null and b/LibExternal/System.Reactive/bin/Debug/net8.0/System.Reactive.dll differ diff --git a/LibExternal/System.Reactive/bin/Debug/net8.0/System.Reactive.pdb b/LibExternal/System.Reactive/bin/Debug/net8.0/System.Reactive.pdb new file mode 100644 index 0000000..7349916 Binary files /dev/null and b/LibExternal/System.Reactive/bin/Debug/net8.0/System.Reactive.pdb differ diff --git a/LibExternal/System.Reactive/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs b/LibExternal/System.Reactive/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs new file mode 100644 index 0000000..2217181 --- /dev/null +++ b/LibExternal/System.Reactive/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")] diff --git a/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.AssemblyInfo.cs b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.AssemblyInfo.cs new file mode 100644 index 0000000..a42b3c7 --- /dev/null +++ b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.AssemblyInfo.cs @@ -0,0 +1,23 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("System.Reactive")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+37e709e30d2205dd559831f65211b8dd5d40a303")] +[assembly: System.Reflection.AssemblyProductAttribute("System.Reactive")] +[assembly: System.Reflection.AssemblyTitleAttribute("System.Reactive")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.AssemblyInfoInputs.cache b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.AssemblyInfoInputs.cache new file mode 100644 index 0000000..98f59b6 --- /dev/null +++ b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +e030c2cc966c92ddfaa980d35631266c231b6f035e09ce76115fde4797da86ff diff --git a/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.GeneratedMSBuildEditorConfig.editorconfig b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..124ddd6 --- /dev/null +++ b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,15 @@ +is_global = true +build_property.TargetFramework = net8.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = System.Reactive +build_property.ProjectDir = C:\KIT\Kit.Core\LibExternal\System.Reactive\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 8.0 +build_property.EnableCodeStyleSeverity = diff --git a/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.GlobalUsings.g.cs b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.GlobalUsings.g.cs new file mode 100644 index 0000000..8578f3d --- /dev/null +++ b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.GlobalUsings.g.cs @@ -0,0 +1,8 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.Strings_Core.resources b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.Strings_Core.resources new file mode 100644 index 0000000..e539b42 Binary files /dev/null and b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.Strings_Core.resources differ diff --git a/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.Strings_Linq.resources b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.Strings_Linq.resources new file mode 100644 index 0000000..f46874b Binary files /dev/null and b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.Strings_Linq.resources differ diff --git a/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.Strings_PlatformServices.resources b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.Strings_PlatformServices.resources new file mode 100644 index 0000000..1612fc5 Binary files /dev/null and b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.Strings_PlatformServices.resources differ diff --git a/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.Strings_Providers.resources b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.Strings_Providers.resources new file mode 100644 index 0000000..de6ece6 Binary files /dev/null and b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.Strings_Providers.resources differ diff --git a/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.assets.cache b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.assets.cache new file mode 100644 index 0000000..4da8b1f Binary files /dev/null and b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.assets.cache differ diff --git a/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.csproj.CoreCompileInputs.cache b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..553ae48 --- /dev/null +++ b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +30a8bcd848ea461092a6ee1f7858c6721036c9bd340f043cdfb5c10b6a37af82 diff --git a/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.csproj.FileListAbsolute.txt b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..ca5e252 --- /dev/null +++ b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.csproj.FileListAbsolute.txt @@ -0,0 +1,16 @@ +C:\KIT\Kit.Core\LibExternal\System.Reactive\bin\Debug\net8.0\System.Reactive.deps.json +C:\KIT\Kit.Core\LibExternal\System.Reactive\bin\Debug\net8.0\System.Reactive.dll +C:\KIT\Kit.Core\LibExternal\System.Reactive\bin\Debug\net8.0\System.Reactive.pdb +C:\KIT\Kit.Core\LibExternal\System.Reactive\obj\Debug\net8.0\System.Reactive.Strings_Core.resources +C:\KIT\Kit.Core\LibExternal\System.Reactive\obj\Debug\net8.0\System.Reactive.Strings_Linq.resources +C:\KIT\Kit.Core\LibExternal\System.Reactive\obj\Debug\net8.0\System.Reactive.Strings_PlatformServices.resources +C:\KIT\Kit.Core\LibExternal\System.Reactive\obj\Debug\net8.0\System.Reactive.Strings_Providers.resources +C:\KIT\Kit.Core\LibExternal\System.Reactive\obj\Debug\net8.0\System.Reactive.csproj.GenerateResource.cache +C:\KIT\Kit.Core\LibExternal\System.Reactive\obj\Debug\net8.0\System.Reactive.GeneratedMSBuildEditorConfig.editorconfig +C:\KIT\Kit.Core\LibExternal\System.Reactive\obj\Debug\net8.0\System.Reactive.AssemblyInfoInputs.cache +C:\KIT\Kit.Core\LibExternal\System.Reactive\obj\Debug\net8.0\System.Reactive.AssemblyInfo.cs +C:\KIT\Kit.Core\LibExternal\System.Reactive\obj\Debug\net8.0\System.Reactive.csproj.CoreCompileInputs.cache +C:\KIT\Kit.Core\LibExternal\System.Reactive\obj\Debug\net8.0\System.Reactive.dll +C:\KIT\Kit.Core\LibExternal\System.Reactive\obj\Debug\net8.0\refint\System.Reactive.dll +C:\KIT\Kit.Core\LibExternal\System.Reactive\obj\Debug\net8.0\System.Reactive.pdb +C:\KIT\Kit.Core\LibExternal\System.Reactive\obj\Debug\net8.0\ref\System.Reactive.dll diff --git a/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.csproj.GenerateResource.cache b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.csproj.GenerateResource.cache new file mode 100644 index 0000000..771a459 Binary files /dev/null and b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.csproj.GenerateResource.cache differ diff --git a/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.dll b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.dll new file mode 100644 index 0000000..7b8ccea Binary files /dev/null and b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.dll differ diff --git a/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.pdb b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.pdb new file mode 100644 index 0000000..7349916 Binary files /dev/null and b/LibExternal/System.Reactive/obj/Debug/net8.0/System.Reactive.pdb differ diff --git a/LibExternal/System.Reactive/obj/Debug/net8.0/ref/System.Reactive.dll b/LibExternal/System.Reactive/obj/Debug/net8.0/ref/System.Reactive.dll new file mode 100644 index 0000000..00f3ec8 Binary files /dev/null and b/LibExternal/System.Reactive/obj/Debug/net8.0/ref/System.Reactive.dll differ diff --git a/LibExternal/System.Reactive/obj/Debug/net8.0/refint/System.Reactive.dll b/LibExternal/System.Reactive/obj/Debug/net8.0/refint/System.Reactive.dll new file mode 100644 index 0000000..00f3ec8 Binary files /dev/null and b/LibExternal/System.Reactive/obj/Debug/net8.0/refint/System.Reactive.dll differ diff --git a/LibExternal/System.Reactive/obj/System.Reactive.csproj.nuget.dgspec.json b/LibExternal/System.Reactive/obj/System.Reactive.csproj.nuget.dgspec.json new file mode 100644 index 0000000..ed55fac --- /dev/null +++ b/LibExternal/System.Reactive/obj/System.Reactive.csproj.nuget.dgspec.json @@ -0,0 +1,74 @@ +{ + "format": 1, + "restore": { + "C:\\KIT\\Kit.Core\\LibExternal\\System.Reactive\\System.Reactive.csproj": {} + }, + "projects": { + "C:\\KIT\\Kit.Core\\LibExternal\\System.Reactive\\System.Reactive.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\KIT\\Kit.Core\\LibExternal\\System.Reactive\\System.Reactive.csproj", + "projectName": "System.Reactive", + "projectPath": "C:\\KIT\\Kit.Core\\LibExternal\\System.Reactive\\System.Reactive.csproj", + "packagesPath": "C:\\Users\\user\\.nuget\\packages\\", + "outputPath": "C:\\KIT\\Kit.Core\\LibExternal\\System.Reactive\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\user\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "C:\\GIT\\RiskProf.Modules.Core\\RiskProf.LK.Back\\RiskProf.LK.Back\\packages": {}, + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.302/PortableRuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/LibExternal/System.Reactive/obj/System.Reactive.csproj.nuget.g.props b/LibExternal/System.Reactive/obj/System.Reactive.csproj.nuget.g.props new file mode 100644 index 0000000..020441f --- /dev/null +++ b/LibExternal/System.Reactive/obj/System.Reactive.csproj.nuget.g.props @@ -0,0 +1,16 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\user\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages + PackageReference + 6.14.0 + + + + + + \ No newline at end of file diff --git a/LibExternal/System.Reactive/obj/System.Reactive.csproj.nuget.g.targets b/LibExternal/System.Reactive/obj/System.Reactive.csproj.nuget.g.targets new file mode 100644 index 0000000..3dc06ef --- /dev/null +++ b/LibExternal/System.Reactive/obj/System.Reactive.csproj.nuget.g.targets @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/LibExternal/System.Reactive/obj/project.assets.json b/LibExternal/System.Reactive/obj/project.assets.json new file mode 100644 index 0000000..31bc412 --- /dev/null +++ b/LibExternal/System.Reactive/obj/project.assets.json @@ -0,0 +1,80 @@ +{ + "version": 3, + "targets": { + "net8.0": {} + }, + "libraries": {}, + "projectFileDependencyGroups": { + "net8.0": [] + }, + "packageFolders": { + "C:\\Users\\user\\.nuget\\packages\\": {}, + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\KIT\\Kit.Core\\LibExternal\\System.Reactive\\System.Reactive.csproj", + "projectName": "System.Reactive", + "projectPath": "C:\\KIT\\Kit.Core\\LibExternal\\System.Reactive\\System.Reactive.csproj", + "packagesPath": "C:\\Users\\user\\.nuget\\packages\\", + "outputPath": "C:\\KIT\\Kit.Core\\LibExternal\\System.Reactive\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\user\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "C:\\GIT\\RiskProf.Modules.Core\\RiskProf.LK.Back\\RiskProf.LK.Back\\packages": {}, + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.302/PortableRuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/LibExternal/System.Reactive/obj/project.nuget.cache b/LibExternal/System.Reactive/obj/project.nuget.cache new file mode 100644 index 0000000..550e4f5 --- /dev/null +++ b/LibExternal/System.Reactive/obj/project.nuget.cache @@ -0,0 +1,8 @@ +{ + "version": 2, + "dgSpecHash": "7esuaxNu1HM=", + "success": true, + "projectFilePath": "C:\\KIT\\Kit.Core\\LibExternal\\System.Reactive\\System.Reactive.csproj", + "expectedPackageFiles": [], + "logs": [] +} \ No newline at end of file